Update to v7.55.0

This commit is contained in:
aapanel.com
2025-11-07 15:30:29 +08:00
parent c32414993f
commit b6e4ab8b35
1243 changed files with 48469 additions and 87603 deletions
+448 -235
View File
@@ -8,7 +8,11 @@
# -------------------------------------------------------------------
import fnmatch
import os, sys, time
import json
import subprocess
from _stat import S_ISREG, S_ISDIR, S_ISLNK
from gevent.lock import BoundedSemaphore
import contextlib
# ------------------------------
# Docker安全检测
@@ -32,6 +36,259 @@ def auto_progress(func):
return wrapper
class DockerImageInspector:
def __init__(self):
self.image_info = None
self.image_id = None
self._container_lock = BoundedSemaphore(1)
self._active_containers = set()
def open_image_by_id(self, image_id):
"""打开指定ID的镜像"""
self.image_id = image_id
return self
def get_image_info(self):
"""获取镜像详细信息"""
try:
cmd = f"docker inspect {self.image_id}"
result = subprocess.run(cmd.split(), capture_output=True, text=True)
# public.print_log("|===========result:{}".format(result))
if result.returncode == 0:
self.image_info = json.loads(result.stdout)[0]
return self.image_info
return None
except Exception as e:
print(f"获取镜像信息失败: {str(e)}")
return None
def ocispec_v1(self):
"""
获取镜像的OCI规范信息
返回包含配置和历史记录的字典
"""
if not self.image_info:
self.get_image_info()
if not self.image_info:
return {}
try:
# 获取配置信息
config = self.image_info.get('Config', {})
# 构造OCI规范格式
oci_spec = {
'created': self.image_info.get('Created'),
'architecture': self.image_info.get('Platform', 'amd64'), # 默认amd64
'os': self.image_info.get('Platform', 'linux'), # 默认linux
'config': {
'Env': config.get('Env', []),
'Cmd': config.get('Cmd', []),
'WorkingDir': config.get('WorkingDir', ''),
'Entrypoint': config.get('Entrypoint', []),
'ExposedPorts': config.get('ExposedPorts', {}),
'Volumes': config.get('Volumes', {}),
},
'history': []
}
# 使用docker history命令获取历史记录
try:
cmd = f"docker history --no-trunc --format '{{{{.CreatedBy}}}}' {self.image_id}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode == 0:
history_lines = result.stdout.strip().split('\n')
for line in history_lines:
if line: # 跳过空行
history_entry = {
'created': '', # 历史记录中可能没有具体时间
'created_by': line,
'empty_layer': line.startswith('#(nop)') # 判断是否为空层
}
oci_spec['history'].append(history_entry)
except Exception as e:
print(f"获取历史记录失败: {str(e)}")
return oci_spec
except Exception as e:
print(f"获取OCI规范信息失败: {str(e)}")
return {}
def reporefs(self):
"""获取镜像的仓库引用名称列表"""
if not self.image_info:
self.get_image_info()
refs = []
if self.image_info and 'RepoTags' in self.image_info:
refs.extend(self.image_info['RepoTags'])
return refs
def id(self):
"""获取镜像ID"""
return self.image_id
@contextlib.contextmanager
def _temp_container(self):
"""创建临时容器的上下文管理器"""
container_id = None
try:
with self._container_lock:
# 创建临时容器
cmd = f"docker create {self.image_id}"
result = subprocess.run(cmd.split(), capture_output=True, text=True)
if result.returncode != 0:
raise Exception(f"Failing to create a temporary container: {result.stderr}")
container_id = result.stdout.strip()
self._active_containers.add(container_id)
yield container_id
finally:
if container_id:
with self._container_lock:
try:
# 清理临时容器
subprocess.run(['docker', 'rm', container_id],
capture_output=True,
check=True)
self._active_containers.remove(container_id)
except Exception as e:
# public.print_log(f"清理临时容器失败 {container_id}: {str(e)}")
pass
def listdir(self, path):
"""列出指定路径下的所有文件和目录"""
try:
with self._temp_container() as container_id:
cmd = f"docker export {container_id} | tar -t"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
files = set()
for line in result.stdout.splitlines():
line = line.strip().strip('/')
if not line:
continue
rel_path = os.path.relpath(line, path.strip('/'))
if rel_path.startswith('..'):
continue
parts = rel_path.split('/')
if len(parts) == 1:
files.add(parts[0])
return list(files)
except Exception as e:
return []
def walk(self, top):
"""遍历目录树"""
try:
with self._temp_container() as container_id:
cmd = f"docker export {container_id} | tar -t"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
dir_tree = {}
for line in result.stdout.splitlines():
line = line.strip().strip('/')
if not line or not line.startswith(top.strip('/')):
continue
parts = line.split('/')
current_path = ''
for i, part in enumerate(parts):
parent_path = current_path
current_path = os.path.join(current_path, part) if current_path else part
if current_path not in dir_tree:
dir_tree[current_path] = {'dirs': set(), 'files': set()}
if i < len(parts) - 1:
dir_tree[parent_path]['dirs'].add(part)
else:
dir_tree[parent_path]['files'].add(part)
for dirpath in sorted(dir_tree.keys()):
if not dirpath.startswith(top.strip('/')):
continue
full_path = '/' + dirpath
dirs = sorted(dir_tree[dirpath]['dirs'])
files = sorted(dir_tree[dirpath]['files'])
yield full_path, dirs, files
except Exception as e:
yield top, [], []
def stat(self, path):
"""获取文件状态信息"""
try:
with self._temp_container() as container_id:
cmd = f"docker export {container_id} | tar -tvf - {path.lstrip('/')}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
raise Exception("The file does not exist")
info = result.stdout.strip().split(None, 5)[0:5]
mode, uid, gid, size = info[0], info[2], info[3], info[4]
class StatResult:
def __init__(self, mode, size, uid, gid):
self.st_mode = int(mode, 8)
self.st_size = int(size)
self.st_uid = int(uid)
self.st_gid = int(gid)
return StatResult(mode, size, uid, gid)
except Exception as e:
raise
def open(self, path, mode="rb"):
"""打开文件"""
try:
with self._temp_container() as container_id:
cmd = f"docker export {container_id} | tar -xOf - {path.lstrip('/')}"
result = subprocess.run(cmd, shell=True, capture_output=True)
if result.returncode != 0:
raise Exception("Unable to read the file")
class FileWrapper:
def __init__(self, data):
self.data = data
self.position = 0
def read(self):
return self.data
def close(self):
pass
return FileWrapper(result.stdout)
except Exception as e:
raise
def cleanup(self):
"""清理所有活动的临时容器"""
with self._container_lock:
for container_id in list(self._active_containers):
try:
subprocess.run(['docker', 'rm', container_id],
capture_output=True,
check=True)
self._active_containers.remove(container_id)
except Exception as e:
# public.print_log(f"Cleaning up temporary containers failed {container_id}: {str(e)}")
pass
class main(dockerBase):
progress_percent = 0 # 扫描进度条
ids_percent = 0
@@ -133,6 +390,50 @@ class main(dockerBase):
return 0
return self.scan_score
def fix_libdl_dependency(self):
"""修复libdl.so依赖"""
try:
# 检查系统中的libdl文件
result, _ = public.ExecShell("find /usr/lib /usr/lib64 /lib /lib64 -name 'libdl.so*' 2>/dev/null")
libdl_files = [x for x in result.strip().split('\n') if x]
if not libdl_files:
# 如果没有找到任何libdl.so文件,需要安装
sys_ver = public.get_os_version()
if "Ubuntu" in sys_ver or "Debian" in sys_ver:
public.ExecShell("apt-get update && apt-get install -y libc6-dev")
elif "CentOS" in sys_ver:
public.ExecShell("yum install -y glibc-devel")
# 重新检查
result, _ = public.ExecShell("find /usr/lib /usr/lib64 /lib /lib64 -name 'libdl.so*' 2>/dev/null")
libdl_files = [x for x in result.strip().split('\n') if x]
if not libdl_files:
return False
# 找到libdl.so.2文件
libdl_so2 = None
for f in libdl_files:
if 'libdl.so.2' in f:
libdl_so2 = f
break
if not libdl_so2:
return False
# 创建软链接
target_dirs = ['/usr/lib', '/usr/lib64', '/lib', '/lib64']
for dir_path in target_dirs:
if os.path.exists(dir_path):
link_path = os.path.join(dir_path, 'libdl.so')
if not os.path.exists(link_path):
public.ExecShell(f"ln -sf {libdl_so2} {link_path}")
return True
except Exception as e:
return False
def image_safe_scan(self, get):
"""
@name 镜像安全扫描入口函数
@@ -150,87 +451,32 @@ class main(dockerBase):
image_id = get.image_id
# 初始化时间
self.send_time = time.time()
# 初始化安装检测SDK
try:
from veinmind import docker
except Exception as e:
public.print_log("Importing veinmind failed:{}".format(e))
# requirements_list = ["veinmind"]
self.send_image_ws(get, msg=public.lang("The detection engine is being initialized. The first load may take a long time...."), status=1)
shell_command = "btpip install --no-dependencies {}".format("veinmind")
public.ExecShell(shell_command)
sys_ver = public.get_os_version()
if "Ubuntu" in sys_ver or "Debian" in sys_ver:
public.WriteFile("/etc/apt/sources.list.d/libveinmind.list",
"deb [trusted=yes] https://download.veinmind.tech/libveinmind/apt/ ./")
self.send_image_ws(get, msg=public.lang("Apt-get is being updated. The first load may take a long time...."), status=1)
public.ExecShell("apt-get update")
time.sleep(1)
self.send_image_ws(get, msg=public.lang("Detection engine being installed, first execution may take thousands of years..."), status=1)
public.ExecShell("apt-get install -y libveinmind-dev")
time.sleep(1)
elif "CentOS" in sys_ver:
public.WriteFile("/etc/yum.repos.d/libveinmind.repo", """[libveinmind]
name=libVeinMind SDK yum repository
baseurl=https://download.veinmind.tech/libveinmind/yum/
enabled=1
gpgcheck=0""")
self.send_image_ws(get, msg=public.lang("The yum cache is being updated. The first load may take a long time...."), status=1)
public.ExecShell("yum makecache")
self.send_image_ws(get, msg=public.lang("Detection engine being installed, first execution may take thousands of years..."), status=1)
public.ExecShell("yum install -y libveinmind-devel")
# 自定义一个新的DockerImageInspector
docker_obj = DockerImageInspector()
# 打开镜像
image = docker_obj.open_image_by_id(image_id=image_id)
# 获取ref镜像名
refs = image.reporefs()
if len(refs) > 0:
self.image_name = refs[0]
else:
self.send_image_ws(get, msg=public.lang("Unsupported system version {}",sys_ver), status=1)
return public.returnMsg(False, public.lang("Unsupported system version {}\nCurrently only supports Debian, Ubuntu, Centos", sys_ver))
self.send_image_ws(get, msg=public.lang("Checking libdl.so dependent libraries..."), status=1)
result, err = public.ExecShell("whereis libdl.so")
result = result.strip().split(" ")
# public.print_log("The situation of libdl.so library:{}".format(result))
if len(result) <= 1:
# public.print_log("Missing libdl.so library")
result, err = public.ExecShell("whereis libdl.so.2")
result = result.strip().split(" ")
# public.print_log("The situation of libdl.so.2 library:{}".format(result))
if len(result) <= 1:
public.print_log("Missing libdl.so libraryRequires libdl.so or libdl.so2 to be installed")
public.returnMsg(False, "Missing libdl.so libraryRequires libdl.so or libdl.so2 to be installed")
else:
# 建立libdl.so软链接至libdl.so.2
for lib in result[1:]:
ln_command = "ln -s {} {}".format(lib, lib[:-2])
# public.print_log("Soft link in progress{}".format(ln_command))
public.ExecShell(ln_command)
from veinmind import docker
self.image_name = image.id()
public.print_log("Detecting:{}".format(self.image_name))
self.send_image_ws(get, msg=public.lang("Scanning {} exception history command", self.image_name))
self.scan_history(get, image)
self.send_image_ws(get, msg=public.lang("Scanning {} sensitive information", self.image_name))
self.scan_sensitive(get, image)
self.send_image_ws(get, msg=public.lang("Scanning {} backdoor", self.image_name))
self.scan_backdoor(get, image)
self.send_image_ws(get, msg=public.lang("Scanning {} container escapes"))
self.scan_escape(get, image)
self.send_image_ws(get, msg=public.lang("{}Scan completed", self.image_name), end=True)
except Exception as e:
self.send_image_ws(get, msg=public.lang("Scanning the image failed:{}", str(e)), end=True)
# 开始检测
# 获取docker对象
docker_obj = docker.Docker()
# # 获取所有镜像id
# ids = docker_obj.list_image_ids()
# # 计算镜像进度占比
# self.ids_percent = math.floor(100 / len(ids))
# # 计算每个镜像扫描进度占比
# self.scan_percent = math.floor(self.ids_percent / 3)
# 开始镜像检测
# for key, id in enumerate(ids):
image = docker_obj.open_image_by_id(image_id=image_id)
# 获取ref镜像名
refs = image.reporefs()
if len(refs) > 0:
self.image_name = refs[0]
else:
self.image_name = image.id()
public.print_log("Detecting:{}".format(self.image_name))
self.send_image_ws(get, msg=public.lang("Scanning {} exception history command",self.image_name))
self.scan_history(get, image)
self.send_image_ws(get, msg=public.lang("Scanning {} sensitive information",self.image_name))
self.scan_sensitive(get, image)
self.send_image_ws(get, msg=public.lang("Scanning {} backdoor",self.image_name))
self.scan_backdoor(get, image)
self.send_image_ws(get, msg=public.lang("Scanning {} container escapes"))
self.scan_escape(get, image)
self.send_image_ws(get, msg=public.lang("{}Scan completed",self.image_name), end=True)
def scan_history(self, get, image):
"""
@@ -243,8 +489,13 @@ gpgcheck=0""")
"WORKDIR", "ARG", "ONBUILD", "STOPSIGNAL", "HEALTHCHECK", "SHELL")
rules = {
"rules": [{"description": "Miner Repo", "instruct": "RUN", "match": ".*(xmrig|ethminer|miner)\\.git.*"},
{"description": "Unsafe Path", "instruct": "ENV", "match": "PATH=.*(|:)(/tmp|/dev/shm)"}]}
{"description": "Unsafe Path", "instruct": "ENV", "match": "PATH=.*(|:)(/tmp|/dev/shm)"},
{
"description": "MySQL Shell Installation",
"instruct": "CMD"
}
]
}
ocispec = image.ocispec_v1()
if 'history' in ocispec.keys() and len(ocispec['history']) > 0:
for history in ocispec['history']:
@@ -258,10 +509,21 @@ gpgcheck=0""")
if len(command_split) == 2:
instruct = command_split[0]
command_content = command_split[1]
# 如果是JSON格式的字符串,尝试解析
if command_content.startswith('[') and command_content.endswith(']'):
import json
parsed_content = json.loads(command_content)
if isinstance(parsed_content, list):
command_content = ' '.join(parsed_content)
# 移除引号和方括号
command_content = command_content.strip('[]"\' ')
# command_content = " ".join(command_split[1:])
for r in rules["rules"]:
if r["instruct"] == instruct:
if re.match(r["match"], command_content):
self.send_image_ws(get, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(command_content)), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
self.send_image_ws(get,status=0, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(command_content)), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
break
else:
instruct = command_split[0]
@@ -269,7 +531,7 @@ gpgcheck=0""")
for r in rules["rules"]:
if r["instruct"] == instruct:
if re.match(r["match"], command_content):
self.send_image_ws(get, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(command_content)), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
self.send_image_ws(get,status=0, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(command_content)), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
break
else:
command_split = created_by.split()
@@ -277,13 +539,13 @@ gpgcheck=0""")
for r in rules["rules"]:
if r["instruct"] == command_split[0]:
if re.match(r["match"], " ".join(command_split[1:])):
self.send_image_ws(get, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(" ".join(command_split[1:]))), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
self.send_image_ws(get,status=0, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(" ".join(command_split[1:]))), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
break
else:
for r in rules["rules"]:
if r["instruct"] == "RUN":
if re.match(r["match"], created_by):
self.send_image_ws(get, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(created_by)), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
self.send_image_ws(get,status=0, msg=public.lang("Suspicious abnormal history command found"), detail=public.lang("It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.",self.short_string(created_by)), repair=public.lang("1.It is recommended to check whether the command is required for normal business<br/>2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses."))
break
def scan_sensitive(self, get, image):
@@ -582,160 +844,113 @@ gpgcheck=0""")
{"id": 131, "name": "prefect-api-token", "description": "Prefect API token", "match": "pnu_[a-z0-9]{36}",
"level": "medium"}]}
# refs = image.reporefs()
# if len(refs) > 0:
# ref = refs[0]
# else:
# ref = image.id()
# public.print_log("start scan sensitive: " + ref)
# 排除系统目录
EXCLUDE_DIRS = {
"usr", "lib", "lib32", "lib64", "boot", "run", "media",
"proc", "sys", "dev", "bin", "sbin", "home"
}
# 检测docker历史(另外有扫描项)
# ocispec = image.ocispec_v1()
# if 'history' in ocispec.keys() and len(ocispec['history']) > 0:
# for history in ocispec['history']:
# command_content = history['created_by']
# report_rule_list = []
# for r in rules["rules"]:
# # 正则选择 可以选择docker history的正则是由哪些模块检测
# for i in ['env', 'match', 'filepath']:
# regexp_s = r.get(i)
# if not regexp_s:
# continue
# if re.search(regexp_s, command_content, re.IGNORECASE):
# self.send_image_ws(get, msg="镜像OCI存在敏感信息", detail=r["description"]+"镜像OCI存在敏感信息{}".format(command_content), repair="使用此镜像可能存在危险,建议更换同类型镜像或是部署容器时清理敏感信息", status=2)
# if not report_rule_list:
# continue
# 检测env环境变量
# ocispec = image.ocispec_v1()
# if 'config' in ocispec.keys() and 'Env' in ocispec['config'].keys():
# env_list = image.ocispec_v1()['config']['Env']
# for env in env_list:
# env_split = env.split("=")
# if len(env_split) >= 2:
# for r in rules["rules"]:
# if "env" in r.keys():
# env_regex = r["env"]
# if re.match(env_regex, env, re.IGNORECASE):
# self.send_image_ws(get, msg="镜像OCI存在敏感信息", detail=r["description"]+"\nenv存在敏感信息,可能会造成数据泄露", repair="使用该镜像部署容器时,及时修改默认密码或者鉴权值,防止被黑客利用入侵", status=2)
# break
# 排除大型项目
large_dir = ["usr", "lib"]
sensitive_dirs = []
def get_scan_dirs():
"""获取需要扫描的目录列表"""
try:
root_contents = image.listdir("/")
return ["/" + d for d in root_contents if d not in EXCLUDE_DIRS]
except Exception as e:
return ["/"]
def check_file_content(filepath, content, rules):
"""检查文件内容是否匹配敏感规则"""
try:
text_content = content.decode('utf-8', errors='ignore')
for rule in rules["rules"]:
if "match" in rule and re.search(rule["match"], text_content):
self.send_image_ws(
get,
msg=f"Discover sensitive information: {filepath}",
detail=f"{rule['description']}: Sensitive information was found in the file {filepath}.",
repair="1. Check and clean up sensitive information \n 2. Use environment variables or a secure key management system",
status=2
)
return True
return False
except:
return False
def check_path_pattern(path, rules):
"""检查路径是否匹配敏感规则"""
for rule in rules["rules"]:
if "filepath" in rule and re.match(rule["filepath"], path):
self.send_image_ws(
get,
msg=f"Discover sensitive {'a directory' if os.path.isdir(path) else 'a file'}: {path}",
detail=f"{rule['description']}: A sensitive path {path} has been detected, which may pose a security risk",
repair="1. Check and delete unnecessary sensitive files/directories\n2. Limit access permissions\n3. Encrypt the storage of sensitive information",
status=2
)
return True
return False
try:
for filename in image.listdir("/"):
if filename not in large_dir:
sensitive_dirs.append("/"+filename)
except Exception as e:
public.print_log("Failed to obtain mirror directory{}".format(e))
sensitive_dirs = ["/"]
# 1. 获取待扫描目录
scan_dirs = get_scan_dirs()
# 检测存在敏感数据的路径
for sensitive_dir in sensitive_dirs:
for root, dirs, files in image.walk(sensitive_dir):
# 短暂停留0.004s,防止占用太高影响其他接口响应
time.sleep(0.004)
# 遍历深度不超过3
if len(root.split("/")) > 3:
self.send_image_ws(get, msg=self.short_string1(public.lang("Scanning:{}",root)))
# public.print_log("跳过{}".format(root))
# 2. 遍历每个目录
for base_dir in scan_dirs:
try:
for root, dirs, files in image.walk(base_dir):
# 控制遍历深度
if len(root.split("/")) > 3:
continue
# 发送进度消息
self.send_image_ws(get, msg=f"Scanning: {root}")
# 跳过空目录
if not dirs and not files:
continue
# 检查目录
for dirname in dirs[:]: # 使用切片创建副本
dirpath = os.path.join(root, dirname)
check_path_pattern(dirpath, rules)
# 检查文件
for filename in files:
try:
filepath = os.path.join(root, filename)
# 跳过白名单文件
if any(fnmatch.fnmatch(filepath, wp) for wp in rules["whitelist"]["paths"]):
continue
# 检查文件状态
try:
f_stat = image.stat(filepath)
if not S_ISREG(f_stat.st_mode) or f_stat.st_size > 10 * 1024 * 1024:
continue
# 读取文件内容
with image.open(filepath, mode="rb") as f:
content = f.read()
if not content: # 跳过空文件
continue
# 检查文件路径和内容
if check_path_pattern(filepath, rules):
continue
check_file_content(filepath, content, rules)
except Exception as e:
continue
except Exception as e:
continue
except Exception as e:
continue
for dir in dirs:
try:
dirpath = os.path.join(root, dir)
self.send_image_ws(get, msg=public.lang("Scanning:{}...",dirpath))
# public.print_log("扫描目录{}".format(dirpath))
# detect filepath or filename
for r in rules["rules"]:
if "filepath" in r.keys():
filepath_match_regex = r["filepath"]
if re.match(filepath_match_regex, dirpath):
self.send_image_ws(get, msg=public.lang("Sensitive directories found{}", dirpath), detail=public.lang("The image exists in a sensitive directory:{}, may be used by attackers to steal sensitive data or source code, leading to further security issues.",dirpath), repair=public.lang("1. Enter the container deployed using the image and delete the directory without affecting the business<br/> 2. If it cannot be deleted, restrict access to the directory",dirpath), status=2)
break
except Exception as e:
pass
# public.print_log("Match sensitive information to catch exceptions{}".format(e))
for filename in files:
try:
filepath = os.path.join(root, filename)
self.send_image_ws(get, msg=public.lang("Scanning:{}...",filename))
# public.print_log("扫描文件{}".format(filepath))
# 跳过白名单
whitelist = rules["whitelist"]
white_match = False
white_paths = whitelist["paths"]
for wp in white_paths:
if fnmatch.filter([filepath], wp):
white_match = True
break
if white_match:
continue
try:
# 跳过非常规文件,超过10m
f_stat = image.stat(filepath)
if not S_ISREG(f_stat.st_mode):
continue
if f_stat.st_size > 10 * 1024 * 1024:
continue
f = image.open(filepath, mode="rb")
f_content_byte = f.read()
except FileNotFoundError as e:
# public.print_log("Error while traversing sensitive files{}".format(e))
continue
# except BaseException as e:
# public.print_log("Error while traversing sensitive files{}".format(e))
# continue
# 检测文件路径及文件名
match = False
for r in rules["rules"]:
if "filepath" in r.keys():
filepath_match_regex = r["filepath"]
if re.match(filepath_match_regex, filepath):
match = True
self.send_image_ws(get, msg=public.lang("Sensitive files found{}",filepath), detail=r["description"] + "<br/>It was found that the image contains sensitive files {}, which may cause leakage.".format(filepath), repair="1. Enter the container deployed using the image, and it is recommended to delete the file if it does not affect the business<br/> 2. If it cannot be deleted, restrict the access rights of the file<br/> 3. Use a password to protect sensitive files from being easily accessed read", status=2)
break
if match:
continue
# chardet_guess = chardet.detect(f_content_byte[0:64])
# if chardet_guess["encoding"] != None:
# try:
# f_content = f_content_byte.decode(chardet_guess["encoding"])
# except:
# continue
# else:
# f_content = str(f_content_byte)
# mime_guess = magic.from_buffer(f_content_byte, mime=True)
# for r in rules["rules"]:
# # mime
# mime_find = False
# if "mime" in r.keys():
# if r["mime"] == mime_guess:
# mime_find = True
# else:
# if mime_guess.startswith("text/"):
# mime_find = True
# if mime_find:
# if "match" in r.keys():
# match = r["match"]
# if match.startswith("$contains:"):
# keyword = match.lstrip("$contains:")
# if keyword in f_content:
# file_stat = image.stat(filepath)
# self.send_image_ws(get, msg="镜像路径{}存在敏感信息".format(filepath),
# repair="建议使用该镜像部署容器后,检查该文件是否有用,无用则清理",
# status=2)
#
# else:
# if re.match(match, f_content):
# file_stat = image.stat(filepath)
# self.send_image_ws(get, msg="镜像路径{}存在敏感信息".format(filepath),
# repair="建议使用该镜像部署容器后,检查该文件是否有用,无用则清理",
# status=2)
except Exception as e:
pass
# public.print_log("Error while traversing sensitive files{}".format(e))
except Exception as e:
# public.print_log(f"|===========扫描失败: {str(e)}")
pass
def scan_backdoor(self, get, image):
"""
@name 扫描后门
@@ -778,7 +993,6 @@ gpgcheck=0""")
continue
except BaseException:
pass
# public.print_log(e)
def crontab():
"""
@@ -869,7 +1083,6 @@ gpgcheck=0""")
continue
except BaseException:
pass
# public.print_log(e)
def sshd():
"""