查看CPU信息：使用命令cat /proc/cpuinfo可以查看CPU的详细信息，包括型号、主频、核心数等。查看内存信息：使用命令cat /proc/meminfo可以查看系统的内存信息，包括总内存、可用内存、缓存等。查看硬盘信息：使用命令fdisk -l可以列出系统中的硬盘信息，包括硬盘的容量、分区情况等。使用命令df -h可以查看文件系统的使用情况，包括磁盘空间的总量、已用空间、可用空间等。查看网络设备信息：使用命令ifconfig可以查看网络设备的配置信息，包括IP地址、子网掩码、网关等。使用命令iwconfig可以查看无线网络设备的配置信息，包括SSID、信号强度等。查看显卡信息：使用命令lspci | grep VGA可以查看系统中的显卡信息，包括显卡型号、厂商等。查看主板信息：使用命令dmidecode -t baseboard可以查看主板的详细信息，包括制造商、型号等。
一、FastDFS介绍FastDFS开源地址：https://github.com/happyfish100参考：分布式文件系统FastDFS设计原理 参考：FastDFS分布式文件系统个人封装的FastDFS Java API：https://github.com/bojiangzhou/lyyzoo-fastdfs-java1、简介FastDFS 是一个开源的高性能分布式文件系统（DFS）。 它的主要功能包括：文件存储，文件同步和文件访问，以及高容量和负载平衡。主要解决了海量数据存储问题，特别适合以中小文件（建议范围：4KB < file_size <500MB）为载体的在线服务。FastDFS 系统有三个角色：跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。 Tracker Server：跟踪服务器，主要做调度工作，起到均衡的作用；负责管理所有的 storage server和 group，每个 storage 在启动后会连接 Tracker，告知自己所属 group 等信息，并保持周期性心跳。 Storage Server：存储服务器，主要提供容量和备份服务；以 group 为单位，每个 group 内可以有多台 storage server，数据互为备份。 Client：客户端，上传下载数据的服务器，也就是我们自己的项目所部署在的服务器。2、FastDFS的存储策略为了支持大容量，存储节点（服务器）采用了分卷（或分组）的组织方式。存储系统由一个或多个卷组成，卷与卷之间的文件是相互独立的，所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成，一个卷下的存储服务器中的文件都是相同的，卷中的多台存储服务器起到了冗余备份和负载均衡的作用。在卷中增加服务器时，同步已有的文件由系统自动完成，同步完成后，系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时，可以动态添加卷。只需要增加一台或多台服务器，并将它们配置为一个新的卷，这样就扩大了存储系统的容量。3、FastDFS的上传过程FastDFS向使用者提供基本文件访问接口，比如upload、download、append、delete等，以客户端库的方式提供给用户使用。Storage Server会定期的向Tracker Server发送自己的存储信息。当Tracker Server Cluster中的Tracker Server不止一个时，各个Tracker之间的关系是对等的，所以客户端上传时可以选择任意一个Tracker。当Tracker收到客户端上传文件的请求时，会为该文件分配一个可以存储文件的group，当选定了group后就要决定给客户端分配group中的哪一个storage server。当分配好storage server后，客户端向storage发送写文件请求，storage将会为文件分配一个数据存储目录。然后为文件分配一个fileid，最后根据以上的信息生成文件名存储文件。4、FastDFS的文件同步写文件时，客户端将文件写至group内一个storage server即认为写文件成功，storage server写完文件后，会由后台线程将文件同步至同group内其他的storage server。每个storage写文件后，同时会写一份binlog，binlog里不包含文件数据，只包含文件名等元信息，这份binlog用于后台同步，storage会记录向group内其他storage同步的进度，以便重启后能接上次的进度继续同步；进度以时间戳的方式进行记录，所以最好能保证集群内所有server的时钟保持同步。storage的同步进度会作为元数据的一部分汇报到tracker上，tracke在选择读storage的时候会以同步进度作为参考。5、FastDFS的文件下载客户端uploadfile成功后，会拿到一个storage生成的文件名，接下来客户端根据这个文件名即可访问到该文件。二、安装FastDFS环境0、前言操作环境：CentOS7 X64，以下操作都是单机环境。我把所有的安装包下载到/opt/下，解压到当前目录。1、下载安装 libfastcommonlibfastcommon是从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库，基础环境，安装即可 。 ① 下载libfastcommoncd /opt wget https://github.com/happyfish100/libfastcommon/archive/V1.0.7.tar.gz② 解压tar -zxvf V1.0.7.tar.gz cd libfastcommon-1.0.7③ 编译、安装./make.sh ./make.sh install④ libfastcommon.so 安装到了/usr/lib64/libfastcommon.so，但是FastDFS主程序设置的lib目录是/usr/local/lib，所以需要创建软链接。ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so2、下载安装FastDFS① 下载FastDFSwget https://github.com/happyfish100/fastdfs/archive/V5.05.tar.gz② 解压tar -zxvf V5.05.tar.gz cd fastdfs-5.05③ 编译、安装./make.sh ./make.sh install④ 默认安装方式安装后的相应文件与目录A、服务脚本：/etc/init.d/fdfs_storaged /etc/init.d/fdfs_trackerdB、配置文件（这三个是作者给的样例配置文件） :/etc/fdfs/client.conf.sample /etc/fdfs/storage.conf.sample /etc/fdfs/tracker.conf.sampleC、命令工具在 /usr/bin/ 目录下：fdfs_appender_test fdfs_appender_test1 fdfs_append_file fdfs_crc32 fdfs_delete_file fdfs_download_file fdfs_file_info fdfs_monitor fdfs_storaged fdfs_test fdfs_test1 fdfs_trackerd fdfs_upload_appender fdfs_upload_file stop.sh restart.sh ⑤ FastDFS 服务脚本设置的 bin 目录是 /usr/local/bin， 但实际命令安装在 /usr/bin/ 下。 两种方式： 》 一是修改FastDFS 服务脚本中相应的命令路径，也就是把 /etc/init.d/fdfs_storaged 和 /etc/init.d/fdfs_trackerd两个脚本中的 /usr/local/bin 修改成 /usr/bin。vim fdfs_trackerd #使用查找替换命令进统一修改:%s+/usr/local/bin+/usr/bin vim fdfs_storaged #使用查找替换命令进统一修改:%s+/usr/local/bin+/usr/bin 》 二是建立 /usr/bin 到 /usr/local/bin 的软链接，我是用这种方式。 ln -s /usr/bin/fdfs_trackerd /usr/local/bin ln -s /usr/bin/fdfs_storaged /usr/local/bin ln -s /usr/bin/stop.sh /usr/local/bin ln -s /usr/bin/restart.sh /usr/local/bin3、配置FastDFS跟踪器(Tracker)配置文件详细说明参考：FastDFS 配置文件详解① 进入 /etc/fdfs，复制 FastDFS 跟踪器样例配置文件 tracker.conf.sample，并重命名为 tracker.conf。cd /etc/fdfs cp tracker.conf.sample tracker.conf vim tracker.conf② 编辑tracker.conf ，标红的需要修改下，其它的默认即可。# 提供服务的端口 port=22122 # Tracker 数据和日志目录地址(根目录必须存在,子目录会自动创建) base_path=/data/fastdfs/tracker # HTTP 服务端口 http.server_port=80③ 创建tracker基础数据目录，即base_path对应的目录mkdir -p /data/fastdfs/tracker④ 防火墙中打开跟踪端口（默认的22122）vim /etc/sysconfig/iptables # 添加如下端口行： -A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT # 重启防火墙： service iptables restart⑤ 启动Tracker初次成功启动，会在 /data/fastdfs/tracker/ (配置的base_path)下创建 data、logs 两个目录。可以用这种方式启动/etc/init.d/fdfs_trackerd start也可以用这种方式启动，前提是上面创建了软链接，后面都用这种方式service fdfs_trackerd start查看 FastDFS Tracker 是否已成功启动 ，22122端口正在被监听，则算是Tracker服务安装成功。netstat -unltp|grep fdfs关闭Tracker命令：service fdfs_trackerd stop⑥ 设置Tracker开机启动chkconfig fdfs_trackerd on 或者：vim /etc/rc.d/rc.local # 加入配置： /etc/init.d/fdfs_trackerd start ⑦ tracker server 目录及文件结构 Tracker服务启动成功后，会在base_path下创建data、logs两个目录。目录结构如下：${base_path} |__data | |__storage_groups.dat：存储分组信息 | |__storage_servers.dat：存储服务器列表 |__logs | |__trackerd.log： tracker server 日志文件4、配置 FastDFS 存储 (Storage)① 进入 /etc/fdfs 目录，复制 FastDFS 存储器样例配置文件 storage.conf.sample，并重命名为 storage.confcd /etc/fdfs cp storage.conf.sample storage.conf vim storage.conf② 编辑storage.conf标红的需要修改，其它的默认即可。# Storage 数据和日志目录地址(根目录必须存在，子目录会自动生成) base_path=/data/fastdfs/storage # 逐一配置 store_path_count 个路径，索引号基于 0。 # 如果不配置 store_path0，那它就和 base_path 对应的路径一样。 store_path0=/data/fastdfs/file # tracker_server 的列表 ，会主动连接 tracker_server # 有多个 tracker server 时，每个 tracker server 写一行 tracker_server=ip:22122 #ip不能是127.0.0.1，可以是公网ip # 访问端口 http.server_port=80 ③ 创建Storage基础数据目录，对应base_path目录mkdir -p /data/fastdfs/storage # 这是配置的store_path0路径 mkdir -p /data/fastdfs/file④ 防火墙中打开存储器端口（默认的 23000）vim /etc/sysconfig/iptables # 添加如下端口行： -A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT重启防火墙：service iptables restart⑤ 启动 Storage启动Storage前确保Tracker是启动的。初次启动成功，会在 /data/fastdfs/storage 目录下创建 data、 logs 两个目录。 可以用这种方式启动/etc/init.d/fdfs_storaged start也可以用这种方式，后面都用这种service fdfs_storaged start查看 Storage 是否成功启动，23000 端口正在被监听，就算 Storage 启动成功。netstat -unltp|grep fdfs关闭Storage命令：service fdfs_storaged stop查看Storage和Tracker是否在通信：/usr/bin/fdfs_monitor /etc/fdfs/storage.conf⑥ 设置 Storage 开机启动chkconfig fdfs_storaged on 或者：vim /etc/rc.d/rc.local # 加入配置： /etc/init.d/fdfs_storaged start⑦ Storage 目录同 Tracker，Storage 启动成功后，在base_path 下创建了data、logs目录，记录着 Storage Server 的信息。在 store_path0 目录下，创建了N*N个子目录：5、文件上传测试① 修改 Tracker 服务器中的客户端配置文件 cd /etc/fdfs cp client.conf.sample client.conf vim client.conf修改如下配置即可，其它默认。# Client 的数据和日志目录 base_path=/data/fastdfs/client # Tracker端口 tracker_server=file.ljzsg.com:22122创建Client基础数据目录，对应base_path目录mkdir -p /data/fastdfs/client② 上传测试 在linux内部执行如下命令上传 namei.jpeg 图片/usr/bin/fdfs_upload_file /etc/fdfs/client.conf namei.jpeg上传成功后返回文件ID号：group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg返回的文件ID由group、存储目录、两级子目录、fileid、文件后缀名（由客户端指定，主要用于区分文件类型）拼接而成。!Pasted image 20240416235048.png三、安装Nginx上面将文件上传成功了，但我们无法下载。因此安装Nginx作为服务器以支持Http方式访问文件。同时，后面安装FastDFS的Nginx模块也需要Nginx环境。Nginx只需要安装到StorageServer所在的服务器即可，用于访问文件。我这里由于是单机，TrackerServer和StorageServer在一台服务器上。1、安装nginx所需环境 ① gcc 安装yum install gcc-c++② PCRE pcre-devel 安装yum install -y pcre pcre-devel③ zlib 安装yum install -y zlib zlib-devel④ OpenSSL 安装yum install -y openssl openssl-devel2、安装Nginx① 下载nginxwget -c https://nginx.org/download/nginx-1.12.1.tar.gz② 解压tar -zxvf nginx-1.12.1.tar.gz cd nginx-1.12.1③ 使用默认配置./configure④ 编译、安装make make install⑤ 启动nginxcd /usr/local/nginx/sbin/ ./nginx # 其它命令 ./nginx -s stop ./nginx -s quit ./nginx -s reload⑥ 设置开机启动vim /etc/rc.local # 添加一行： /usr/local/nginx/sbin/nginx # 设置执行权限 chmod 755 rc.local⑦ 查看nginx的版本及模块/usr/local/nginx/sbin/nginx -V⑧ 防火墙中打开Nginx端口（默认的 80） 添加后就能在本机使用80端口访问了。vim /etc/sysconfig/iptables # 添加如下端口行： -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT # 重启防火墙： service iptables restart3、访问文件简单的测试访问文件① 修改nginx.confvim /usr/local/nginx/conf/nginx.conf添加如下行，将 /group1/M00 映射到 /data/fastdfs/file/datalocation /group1/M00 { alias /data/fastdfs/file/data; } 重启nginx/usr/local/nginx/sbin/nginx -s reload② 在浏览器访问之前上传的图片、成功。访问地址：http://ip/group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg四、FastDFS 配置 Nginx 模块1、安装配置Nginx模块① fastdfs-nginx-module 模块说明 FastDFS 通过 Tracker 服务器，将文件放在 Storage 服务器存储， 但是同组存储服务器之间需要进行文件复制， 有同步延迟的问题。 假设 Tracker 服务器将文件上传到了 192.168.51.128，上传成功后文件 ID已经返回给客户端。 此时 FastDFS 存储集群机制会将这个文件同步到同组存储 192.168.51.129，在文件还没有复制完成的情况下，客户端如果用这个文件 ID 在 192.168.51.129 上取文件,就会出现文件无法访问的错误。 而 fastdfs-nginx-module 可以重定向文件链接到源服务器取文件，避免客户端由于复制延迟导致的文件无法访问错误。② 下载 fastdfs-nginx-module、解压# 这里为啥这么长一串呢，因为最新版的master与当前nginx有些版本问题。 wget https://github.com/happyfish100/fastdfs-nginx-module/archive/5e5f3566bbfa57418b5506aaefbe107a42c9fcb1.zip # 解压 unzip 5e5f3566bbfa57418b5506aaefbe107a42c9fcb1.zip # 重命名 mv fastdfs-nginx-module-5e5f3566bbfa57418b5506aaefbe107a42c9fcb1 fastdfs-nginx-module-master③ 配置Nginx在nginx中添加模块# 先停掉nginx服务 /usr/local/nginx/sbin/nginx -s stop # 进入解压包目录 cd /softpackages/nginx-1.12.1/ # 添加模块 ./configure --add-module=../fastdfs-nginx-module-master/src # 重新编译、安装 make && make install④ 查看Nginx的模块/usr/local/nginx/sbin/nginx -V有下面这个就说明添加模块成功⑤ 复制 fastdfs-nginx-module 源码中的配置文件到/etc/fdfs 目录， 并修改cd /softpackages/fastdfs-nginx-module-master/src cp mod_fastdfs.conf /etc/fdfs/修改如下配置，其它默认vim mod_fastdfs.conf# 连接超时时间 connect_timeout=10 # Tracker Server tracker_server=ip:22122 # StorageServer 默认端口 storage_server_port=23000 # 如果文件ID的uri中包含/group**，则要设置为true url_have_group_name = true # Storage 配置的store_path0路径，必须和storage.conf中的一致 store_path0=/data/fastdfs/file⑥ 复制 FastDFS 的部分配置文件到/etc/fdfs 目录cd /opt/fastdfs-5.05/conf/ cp anti-steal.jpg http.conf mime.types /etc/fdfs/ ⑦ 配置nginx，修改nginx.confvim /usr/local/nginx/conf/nginx.conf修改配置，其它的默认# 在80端口下添加fastdfs-nginx模块 location ~/group([0-9])/M00 { ngx_fastdfs_module; }注意： listen 80 端口值是要与 /etc/fdfs/storage.conf 中的 http.server_port=80 (前面改成80了)相对应。如果改成其它端口，则需要统一，同时在防火墙中打开该端口。 location 的配置，如果有多个group则配置location ~/group([0-9])/M00 ，没有则不用配group。⑧ 在/ljzsg/fastdfs/file 文件存储目录下创建软连接，将其链接到实际存放数据的目录，这一步可以省略。ln -s /opt/fastdfs/file/data/ /opt/fastdfs/file/data/M00 ⑨ 启动nginx/usr/local/nginx/sbin/nginx⑩ 在地址栏访问。能下载文件就算安装成功。注意和第三点中直接使用nginx路由访问不同的是，这里配置 fastdfs-nginx-module 模块，可以重定向文件链接到源服务器取文件。访问地址：http://ip/group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg
1、安装UbuntuUbuntu系统推荐使用 ubuntu-22.04.4-desktop-amd64.iso，这个版本的镜像在Vmware中搭建不会出现VmwareTools失效问题。安装完之后Ubuntu一定要换源,虽然不换的时候在更新软件依赖有时候会使用清华源，但是不稳定。把Ubuntu自带的镜像源全部注释掉，使用清华源，安装软件速度飞起清华源Ubuntu地址：https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/# 默认注释了源码镜像以提高 apt update 速度，如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse # deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse # 预发布软件源，不建议启用 # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse # # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse2、安装docker这个直接使用Vulhub官网的安装方法就行 curl -s https://get.docker.com/ | sh 安装docker-compose这一步是个坑因为上一步安装docker，安装的是最新版本的docker，而docker-compose现在已经是第二版了，官网还是第一版，安装后使用命令会报错。因为docker和docker-compose版本不匹配。使用下面命令下载最新版。apt-get install docker-compose-plugin #命令使用不是用docker-compose docker compose version docker compose --help docker compose build docker compose up -d docker compose down3、docker换源推荐docker换成aliyun的镜像源，因为用其他的镜像源，在build这一环节出现老是超时。而我换成aliyun的就不会有这种情况。vim /etc/docker/daemon.json内容：{ "registry-mirrors": [ "https://XXX.mirror.aliyuncs.com", "https://hub.docker.com/", "https://docker.mirrors.ustc.edu.cn" ] } 阿里云镜像源申请方法网上都是，这里就不提供了。4、docker-compose.yml version问题WARN[0000] /gws/vulhub/httpd/CVE-2021-40438/docker-compose.yml: `version` is obsolete 这个警告不用管，不会对 build 产生影响
Linux入侵检测病毒清理流程1.确认Linux是否是中毒1.使用命令查看当前系统所有进程一般消耗CUP或者内存90%以上的基本都是病毒，常见的挖矿进程：xfsdatad、rshim、YDService.exe、kinsing、kdevtmpfsi、sysupdate、systemxlv、kthreaddi、kthreaddk 等，病毒进程一版都会跟系统进程名称相似，要注意区分top 或 ps aux | less注：top实时监控退出按键：q2.查看隐藏进程如果top命令查看没发现异常，但是服务器依然很卡，服务器资源占用居高不下，那就换个命令，查看是否存在隐藏进程ps -aux --sort=-pcpu|head -10 2. 确定病毒攻击方式1.使用命令查看系统日志：cat /var/log/secure 分析日志文件，看看是否是远程强行破解系统密码，如下：如果是远程登录root，清理病毒后要修改root的密码查看系统其他用户：查看系统用户vim /etc/shadow 或者 vim /etc/passwd # 检查是否有异常的用户名，如果有就删除 userdel -r 用户名查看单个进程的执行日志：more /var/log/cron log | grep “进程名称”3.病毒查杀1.先确定挖矿进程的PID根据 top 查到的病毒进程，利用PID查看一系列的相关守护进程：systemctl status PID 2.根据展示出来的守护进程，从最高层守护开始杀起（先杀父类）。删除最顶层fule守护进程：rm -rf 路径 或 find / -name “_路径_” | xargs rm -rf 有某部分比较刁钻的病毒会报告：rm: cannot remove '文件名称 ': Operation not permitted这种情况可能性一，不在root的权限下，切换root再操作。可能性二：病毒使用了chattr 文件锁定的命令。我们只要先执行文件解锁，然后就可以正常删除了chattr -i 文件名称 ps -ef | grep 守护进程名称 或者 ps -ef | grep 守护进程名称 | grep -v grep kill -9 PID 注意，有部分挖矿在删除时会触发自我复制，所以删除前可以先控制权限和删除定时扫描：chmod 600 守护进程路径 查看定时器，需要在root权限操作：查看所有定时器crontab -l 如果确认linux服务器重来没有建过定时任务，就可以全部清除crontab -r 如果有自定义的定时器就可以编辑：crontab -e 手动删除可疑任务后保存退出很多病毒是会扫描定时器的，所以在清除定时任务之前，需要把病毒守护进程里面的病毒文件先降低权限才会生效 命令：chmod 600 守护进程路径权限降低，清除定时任务 那就可以逐个杀死进程了，由下往上一个个 kill -9 进程号，例如：先杀 9015把进程全干掉后就删除对应文件（根据进程树中的文件路径）3.如果没有守护进程，那就更简单，直接先控制文件权限，然后停止进程，清理定时任务，最后删除病毒路径即可。到此，病毒清理完成。4.其他安全建议：1.禁止root登录使用自定义的用户登录，再切换Root即可2.查看SSH链接公钥，清除所有可疑的公钥。一般只用用户名密码登录的服务器是不会有公钥文件的，如果有也是空文件。查看公钥文件：vim /root/.ssh/authorized_keys解除锁定chattr -i /root/.ssh/authorized_keys编辑权限chmod 777 /root/.ssh/authorized_keys 清空或清除不是自建的authorized_keysvi /root/.ssh/authorized_keys清空后恢复权限chmod 400 /root/.ssh/authorized_keys锁定authorized_keyschattr +i /root/.ssh/authorized_keys防止通过重命名.ssh文件夹绕过设置chattr +i /root/.ssh3.一定要打开防火墙查看防火墙状态命令：systemctl status firewalld 如果不是显示active状态，需要打开防火墙，注意要配置穿透自己需要用到的端口号systemctl start firewalld 4.要尽量禁止redis的远程访问，redis必须要设置访问密码。5.Linux病毒防护软件：安装clamAV：yum -y install clamav 更新病毒库（需要时间很长，大约20~30分钟）：freshclam 查杀当前目录并删除感染的文件：clamscan -r --remove 查杀当前目录并删除感染的文件：clamscan -r clamAV帮助命令：clamscan help
第一步：下载服务器安全狗Linux版官方提供了两个下载方式，本文采用的是 方式2 wget安装 方法1：在安全狗官网直接下载软件安装包（.tar.gz 格式：safedog_linux64.tar.gz） 方法2：采取wget方式下载发布包：wget http://down.safedog.cn/safedog_linux64.tar.gz第二步：解压并安装tar xzvf 压缩包名tar xzvf safedog_linux64.tar.gzls 获取压缩包解压出来的文件名lsmv文件夹名mv safedog_an_linux64_2.8.32947 safedog mv safedog /opt/chmod 给文件夹内所有.py文件加权限chmod +x *.py执行install.py文件开始安装./install.py运行install.py时候遇到报错Need system command 'locate' to install safedog for linux. Installation aborted!报错的意思是缺少命令，这里我们用yum安装一下当前命令yum -y install mlocate第三步：执行以下命令加入服云，告别Linux传统字符界面，体验更多服云功能（fuyun.safedog.cn）：sdcloud -u 服云帐号第四步：完成服务器安全狗安装完成安装后可运行命令sdui进入操作界面。
1 linxu系统中查询端口是否开放1.1 方式1：使用lsof 查看指定端口号是否开放lsof -i:端口号如下，表示10010端口已开放，10011端口未开放。[root@localhost ~]# lsof -i:10010 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 679 root 53u IPv6 36351 0t0 TCP *:rxapi (LISTEN) [root@localhost ~]# lsof -i:10011 [root@localhost ~]# 1.2 方式2：使用netstat 查看端口号查看系统中所有使用tcp协议开放的端口netstat -ntpl查看系统中所有使用udp协议开放的端口netstat -nupl2 防火墙端口即使开放了，外界也不一定能够访问，需要查看下本机的防火墙配置2.1 防火墙服务状态#启动防火墙 systemctl start firewalld #重启防火墙 systemctl restart firewalld #停止防火墙 systemctl stop firewalld #查看防火墙状态 systemctl status firewalld #开机时启用防火墙 systemctl enable firewalld #开机时禁用防火墙 systemctl disable firewalld #查看是否开机启动 systemctl is-enabled firewalld #查看开放端口列表 firewall-cmd --zone=public --list-ports #刷新防火墙规则 firewall-cmd --reload2.2 开放/禁止端口访问权限#开放端口8080，协议类型tcp的访问权限 firewall-cmd --zone=public --add-port=8080/tcp --permanent #关闭端口8080，协议类型tcp的访问权限 firewall-cmd --zone=public --remove-port=8080/tcp --permanent #开放或者关闭，需要刷新防火墙规则 firewall-cmd --reload #查看开放的端口列表 firewall-cmd --zone=public --list-ports3 远试端口是否能够访问测试条件：使用window电脑连通linux设备网络进行测试。假如linux设备地址是192.168.0.100，并且windows电脑可以ping通。3.1 ssh方式windows上打开cmd。输入以下命令：ssh -v root@192.168.0.100 -p <port>3.2 telnet方式windows上打开cmd。输入以下命令：telnet <ip> <port>
SpringBoot 接口签名校验实践[1概念][开放接口]开放接口是指不需要登录凭证就允许被第三方系统调用的接口。为了防止开放接口被恶意调用，开放接口一般都需要验签才能被调用。提供开放接口的系统下面统一简称为"原系统"。[验签]验签是指第三方系统在调用接口之前，需要按照原系统的规则根据所有请求参数生成一个签名（字符串），在调用接口时携带该签名。原系统会验证签名的有效性，只有签名验证有效才能正常调用接口，否则请求会被驳回。[2接口验签调用流程][1. 约定签名算法]第三方系统作为调用方，需要与原系统协商约定签名算法（下面以SHA256withRSA签名算法为例）。同时约定一个名称（callerID），以便在原系统中来唯一标识调用方系统。[2. 颁发非对称密钥对]签名算法约定后之后，原系统会为每一个调用方系统专门生成一个专属的非对称密钥对（RSA密钥对）。私钥颁发给调用方系统，公钥由原系统持有。注意，调用方系统需要保管好私钥（存到调用方系统的后端）。因为对于原系统而言，调用方系统是消息的发送方，其持有的私钥唯一标识了它的身份是原系统受信任的调用方。调用方系统的私钥一旦泄露，调用方对原系统毫无信任可言。[3. 生成请求参数签名]签名算法约定后之后，生成签名的原理如下（活动图）。为了确保生成签名的处理细节与原系统的验签逻辑是匹配的，原系统一般都提供jar包或者代码片段给调用方来生成签名，否则可能会因为一些处理细节不一致导致生成的签名是无效的。[4. 请求携带签名调用]路径参数中放入约定好的callerID，请求头中放入调用方自己生成的签名[3代码设计][1. 签名配置类]相关的自定义yml配置如下。RSA的公钥和私钥可以使用hutool的SecureUtil工具类来生成，注意公钥和私钥是base64编码后的字符串定义一个配置类来存储上述相关的自定义yml配置 `import cn.hutool.crypto.asymmetric.SignAlgorithm; import lombok.Data; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Map; /** * 签名的相关配置 */ @Data @ConditionalOnProperty(value = "secure.signature.enable", havingValue = "true") // 根据条件注入bean @Component @ConfigurationProperties("secure.signature") public class SignatureProps { private Boolean enable; private Map<String, KeyPairProps> keyPair; @Data public static class KeyPairProps { private SignAlgorithm algorithm; private String publicKeyPath; private String publicKey; private String privateKeyPath; private String privateKey; } } `[2. 签名管理类]定义一个管理类，持有上述配置，并暴露生成签名和校验签名的方法。注意，生成的签名是将字节数组进行十六进制编码后的字符串，验签时需要将签名字符串进行十六进制解码成字节数组 `import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.Sign; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import top.ysqorz.signature.model.SignatureProps; import java.nio.charset.StandardCharsets; @ConditionalOnBean(SignatureProps.class) @Component public class SignatureManager { private final SignatureProps signatureProps; public SignatureManager(SignatureProps signatureProps) { this.signatureProps = signatureProps; loadKeyPairByPath(); } /** * 验签。验证不通过可能抛出运行时异常CryptoException * * @param callerID 调用方的唯一标识 * @param rawData 原数据 * @param signature 待验证的签名(十六进制字符串) * @return 验证是否通过 */ public boolean verifySignature(String callerID, String rawData, String signature) { Sign sign = getSignByCallerID(callerID); if (ObjectUtils.isEmpty(sign)) { return false; } // 使用公钥验签 return sign.verify(rawData.getBytes(StandardCharsets.UTF_8), HexUtil.decodeHex(signature)); } /** * 生成签名 * * @param callerID 调用方的唯一标识 * @param rawData 原数据 * @return 签名(十六进制字符串) */ public String sign(String callerID, String rawData) { Sign sign = getSignByCallerID(callerID); if (ObjectUtils.isEmpty(sign)) { return null; } return sign.signHex(rawData); } public SignatureProps getSignatureProps() { return signatureProps; } public SignatureProps.KeyPairProps getKeyPairPropsByCallerID(String callerID) { return signatureProps.getKeyPair().get(callerID); } private Sign getSignByCallerID(String callerID) { SignatureProps.KeyPairProps keyPairProps = signatureProps.getKeyPair().get(callerID); if (ObjectUtils.isEmpty(keyPairProps)) { return null; // 无效的、不受信任的调用方 } return SecureUtil.sign(keyPairProps.getAlgorithm(), keyPairProps.getPrivateKey(), keyPairProps.getPublicKey()); } /** * 加载非对称密钥对 */ private void loadKeyPairByPath() { // 支持类路径配置，形如：classpath:secure/public.txt // 公钥和私钥都是base64编码后的字符串 signatureProps.getKeyPair() .forEach((key, keyPairProps) -> { // 如果配置了XxxKeyPath，则优先XxxKeyPath keyPairProps.setPublicKey(loadKeyByPath(keyPairProps.getPublicKeyPath())); keyPairProps.setPrivateKey(loadKeyByPath(keyPairProps.getPrivateKeyPath())); if (ObjectUtils.isEmpty(keyPairProps.getPublicKey()) || ObjectUtils.isEmpty(keyPairProps.getPrivateKey())) { throw new RuntimeException("No public and private key files configured"); } }); } private String loadKeyByPath(String path) { if (ObjectUtils.isEmpty(path)) { return null; } return IoUtil.readUtf8(ResourceUtil.getStream(path)); } } `[3. 自定义验签注解]有些接口需要验签，但有些接口并不需要，为了灵活控制哪些接口需要验签，自定义一个验签注解 `import java.lang.annotation.*; /** * 该注解标注于Controller类的方法上，表明该请求的参数需要校验签名 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface VerifySignature { } `[4. AOP实现验签逻辑]验签逻辑不能放在拦截器中，因为拦截器中不能直接读取body的输入流，否则会造成后续@RequestBody的参数解析器读取不到body。由于body输入流只能读取一次，因此需要使用ContentCachingRequestWrapper包装请求，缓存body内容（见第5点），但是该类的缓存时机是在@RequestBody的参数解析器中。因此，满足2个条件才能获取到ContentCachingRequestWrapper中的body缓存：接口的入参必须存在@RequestBody读取body缓存的时机必须在@RequestBody的参数解析之后，比如说：AOP、Controller层的逻辑内。注意拦截器的时机是在参数解析之前的综上，标注了@VerifySignature注解的controlle层方法的入参必须存在@RequestBody，AOP中验签时才能获取到body的缓存！ `import cn.hutool.crypto.CryptoException; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.util.ContentCachingRequestWrapper; import top.ysqorz.common.constant.BaseConstant; import top.ysqorz.config.SpringContextHolder; import top.ysqorz.config.aspect.PointCutDef; import top.ysqorz.exception.auth.AuthorizationException; import top.ysqorz.exception.param.ParamInvalidException; import top.ysqorz.signature.model.SignStatusCode; import top.ysqorz.signature.model.SignatureProps; import top.ysqorz.signature.util.CommonUtils; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.util.Map; @ConditionalOnBean(SignatureProps.class) @Component @Slf4j @Aspect public class RequestSignatureAspect implements PointCutDef { @Resource private SignatureManager signatureManager; @Pointcut("@annotation(top.ysqorz.signature.enumeration.VerifySignature)") public void annotatedMethod() { } @Pointcut("@within(top.ysqorz.signature.enumeration.VerifySignature)") public void annotatedClass() { } @Before("apiMethod() && (annotatedMethod() || annotatedClass())") public void verifySignature() { HttpServletRequest request = SpringContextHolder.getRequest(); String callerID = request.getParameter(BaseConstant.PARAM_CALLER_ID); if (ObjectUtils.isEmpty(callerID)) { throw new AuthorizationException(SignStatusCode.UNTRUSTED_CALLER); // 不受信任的调用方 } // 从请求头中提取签名，不存在直接驳回 String signature = request.getHeader(BaseConstant.X_REQUEST_SIGNATURE); if (ObjectUtils.isEmpty(signature)) { throw new ParamInvalidException(SignStatusCode.REQUEST_SIGNATURE_INVALID); // 无效签名 } // 提取请求参数 String requestParamsStr = extractRequestParams(request); // 验签。验签不通过抛出业务异常 verifySignature(callerID, requestParamsStr, signature); } @SuppressWarnings("unchecked") public String extractRequestParams(HttpServletRequest request) { // @RequestBody String body = null; // 验签逻辑不能放在拦截器中，因为拦截器中不能直接读取body的输入流，否则会造成后续@RequestBody的参数解析器读取不到body // 由于body输入流只能读取一次，因此需要使用ContentCachingRequestWrapper包装请求，缓存body内容，但是该类的缓存时机是在@RequestBody的参数解析器中 // 因此满足2个条件才能使用ContentCachingRequestWrapper中的body缓存 // 1. 接口的入参必须存在@RequestBody // 2. 读取body缓存的时机必须在@RequestBody的参数解析之后，比如说：AOP、Controller层的逻辑内。注意拦截器的时机是在参数解析之前的 if (request instanceof ContentCachingRequestWrapper) { ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request; body = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8); } // @RequestParam Map<String, String[]> paramMap = request.getParameterMap(); // @PathVariable ServletWebRequest webRequest = new ServletWebRequest(request, null); Map<String, String> uriTemplateVarNap = (Map<String, String>) webRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return CommonUtils.extractRequestParams(body, paramMap, uriTemplateVarNap); } /** * 验证请求参数的签名 */ public void verifySignature(String callerID, String requestParamsStr, String signature) { try { boolean verified = signatureManager.verifySignature(callerID, requestParamsStr, signature); if (!verified) { throw new CryptoException("The signature verification result is false."); } } catch (Exception ex) { log.error("Failed to verify signature", ex); throw new AuthorizationException(SignStatusCode.REQUEST_SIGNATURE_INVALID); // 转换为业务异常抛出 } } } import org.aspectj.lang.annotation.Pointcut; public interface PointCutDef { @Pointcut("execution(public * top.ysqorz..controller.*.*(..))") default void controllerMethod() { } @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)") default void postMapping() { } @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)") default void getMapping() { } @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)") default void putMapping() { } @Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)") default void deleteMapping() { } @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") default void requestMapping() { } @Pointcut("controllerMethod() && (requestMapping() || postMapping() || getMapping() || putMapping() || deleteMapping())") default void apiMethod() { } } `[5. 解决请求体只能读取一次]解决方案就是包装请求，缓存请求体。SpringBoot也提供了ContentCachingRequestWrapper来解决这个问题。但是第4点中也详细描述了，由于它的缓存时机，所以它的使用有限制条件。也可以参考网上的方案，自己实现一个请求的包装类来缓存请求体 `import lombok.NonNull; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingRequestWrapper; import top.ysqorz.signature.model.SignatureProps; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @ConditionalOnBean(SignatureProps.class) @Component public class RequestCachingFilter extends OncePerRequestFilter { /** * This {@code doFilter} implementation stores a request attribute for * "already filtered", proceeding without filtering again if the * attribute is already there. * * @param request request * @param response response * @param filterChain filterChain * @see #getAlreadyFilteredAttributeName * @see #shouldNotFilter * @see #doFilterInternal */ @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { boolean isFirstRequest = !isAsyncDispatch(request); HttpServletRequest requestWrapper = request; if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) { requestWrapper = new ContentCachingRequestWrapper(request); } filterChain.doFilter(requestWrapper, response); } } `注册过滤器 `import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import top.ysqorz.signature.model.SignatureProps; @Configuration public class FilterConfig { @ConditionalOnBean(SignatureProps.class) @Bean public FilterRegistrationBean<RequestCachingFilter> requestCachingFilterRegistration( RequestCachingFilter requestCachingFilter) { FilterRegistrationBean<RequestCachingFilter> bean = new FilterRegistrationBean<>(requestCachingFilter); bean.setOrder(1); return bean; } } `[6. 自定义工具类] `import cn.hutool.core.util.StrUtil; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; public class CommonUtils { /** * 提取所有的请求参数，按照固定规则拼接成一个字符串 * * @param body post请求的请求体 * @param paramMap 路径参数(QueryString)。形如：name=zhangsan&age=18&label=A&label=B * @param uriTemplateVarNap 路径变量(PathVariable)。形如：/{name}/{age} * @return 所有的请求参数按照固定规则拼接成的一个字符串 */ public static String extractRequestParams(@Nullable String body, @Nullable Map<String, String[]> paramMap, @Nullable Map<String, String> uriTemplateVarNap) { // body: { userID: "xxx" } // 路径参数 // name=zhangsan&age=18&label=A&label=B // => ["name=zhangsan", "age=18", "label=A,B"] // => name=zhangsan&age=18&label=A,B String paramStr = null; if (!ObjectUtils.isEmpty(paramMap)) { paramStr = paramMap.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(entry -> { // 拷贝一份按字典序升序排序 String[] sortedValue = Arrays.stream(entry.getValue()).sorted().toArray(String[]::new); return entry.getKey() + "=" + joinStr(",", sortedValue); }) .collect(Collectors.joining("&")); } // 路径变量 // /{name}/{age} => /zhangsan/18 => zhangsan,18 String uriVarStr = null; if (!ObjectUtils.isEmpty(uriTemplateVarNap)) { uriVarStr = joinStr(",", uriTemplateVarNap.values().stream().sorted().toArray(String[]::new)); } // { userID: "xxx" }#name=zhangsan&age=18&label=A,B#zhangsan,18 return joinStr("#", body, paramStr, uriVarStr); } /** * 使用指定分隔符，拼接字符串 * * @param delimiter 分隔符 * @param strs 需要拼接的多个字符串，可以为null * @return 拼接后的新字符串 */ public static String joinStr(String delimiter, @Nullable String... strs) { if (ObjectUtils.isEmpty(strs)) { return StrUtil.EMPTY; } StringBuilder sbd = new StringBuilder(); for (int i = 0; i < strs.length; i++) { if (ObjectUtils.isEmpty(strs[i])) { continue; } sbd.append(strs[i].trim()); if (!ObjectUtils.isEmpty(sbd) && i < strs.length - 1 && !ObjectUtils.isEmpty(strs[i + 1])) { sbd.append(delimiter); } } return sbd.toString(); } } `
识别代理工具流量特征：从SSR、V2Ray、Clash到Trojan1概述在今天的互联网世界中，代理工具的使用旨在绕过网络审查、保护隐私、提供更安全的网络通信，而其中一些代理工具，如ShadowsocksR（SSR）、V2Ray、Clash和Trojan等，由于其高度定制化和加密特性而备受欢迎。然而，对于一些网络管理员和监管机构来说，他们可能希望能够识别这些代理工具的流量特征，以便监控或限制其使用。本文将介绍如何更加深入地识别这些代理工具的流量特征。 2深度包检测深度包检测是一种常见的用于识别代理工具流量特征的方法。这种方法侧重于对网络数据包的内容进行详细分析，尤其是检测一些特定的加密协议或自定义协议。 ShadowsocksR（SSR）：SSR通常使用自定义的混淆协议来隐藏流量。识别SSR流量的方法包括检测一些特定的标志或数据包格式，但这通常需要深度包检测技术来实现。例如，SSR的数据包中可能包含Base64编码，通过分析数据包的Base64编码是否遵循SSR的特定模式，可以更准确地识别SSR流量。V2Ray：V2Ray也使用自定义协议，其流量特点包括一些特定的数据包结构和加密方式。通过检测这些特征，可以辨别V2Ray流量。例如，V2Ray的数据包通常以VMess协议进行通信，可以识别VMess数据包的特征字段，如alterId等，来确认V2Ray的存在。3特定的流量模式不同的代理工具通常会展现出独特的流量模式，包括数据包大小、通信模式以及流量特点。 Clash：Clash是一种代理工具，它允许用户配置多种代理协议，包括HTTP、SOCKS、VMess等。通过观察流量的目标端口和通信模式，可以推测出Clash的可能存在。例如，如果流量以HTTP协议进行通信，那么可能是Clash的迹象。Trojan：Trojan代理工具也有一些流量特征，例如使用的自定义协议和端口，以及加密方式。这些特点可以被用于识别Trojan流量。举例来说，Trojan通常使用自定义的密码加密流量，通过检测流量中的特定密码标识，可以确定Trojan的存在。4端口扫描代理工具通常使用非标准端口进行通信，而不是常见的HTTP或HTTPS端口。因此，监控非常用端口的流量可能有助于识别这些代理工具。端口号：ShadowsocksR、V2Ray和Trojan等代理工具通常会使用自定义端口号，而不是常见的80或443端口。监控端口号的活动可以帮助识别代理工具的流量。例如，如果网络上的非标准端口出现了大量传输数据的活动，那么可能存在代理工具的使用。5行为分析行为分析是一种重要的方法，它关注的是网络中终端和流量的行为特征，而不仅仅是数据包的结构或端口号。以下是关于行为分析在识别代理工具流量特征中的方法：数据传输频率：代理工具通常被用于加速网络访问或绕过审查。因此，它们可能在相对短的时间内传输大量数据。通过检测网络中数据传输频率异常高的终端，可以怀疑可能存在代理工具的使用。这种高频率传输数据的行为可能是识别代理工具的一个关键指标。响应时间：代理工具的特点之一是快速响应。在网络通信中，代理工具通常能够更快地建立连接和传输数据。通过监测终端的响应时间，可以检测到网络中是否存在代理工具。如果某个终端在建立连接和传输数据时的响应时间明显较短，可能是代理工具的存在信号。目标端口扫描：代理工具通常会在不同的端口上建立连接，以绕过网络防火墙或审查。通过监测终端对不同端口的连接行为，可以检测出代理工具的存在。代理工具可能会尝试多个端口，以找到一个可以正常工作的通道。数据流模式：代理工具通常会呈现出独特的数据流模式，这些模式可能与正常的网络通信模式有所不同。行为分析可以涵盖这些不同之处，以检测代理工具的存在。通过观察数据流的模式，可以识别出异常的、可能是代理工具引起的数据流。活动时间：代理工具的使用可能会在某些时间段内更为活跃，因此监控终端的活动时间可以帮助确定代理工具的存在。如果某个终端在非常规的时间段内产生大量的网络活动，这可能是代理工具的使用的迹象。非标准协议：代理工具通常使用非标准的或自定义协议进行通信。通过分析终端的通信协议，可以检测出非常见的协议，这可能是代理工具的存在迹象。需要指出的是，行为分析是一项综合性工作，需要对网络流量和终端行为有深入的理解。同时，正常的网络活动也可能具有类似的特点，因此需要与其他方法结合使用，以提高识别的准确性。6流量统计通过统计网络流量，特别关注未知流量来源或流量模式不规律的情况，也可以用来识别代理工具的流量特征。 不规律的流量模式：一些代理工具的流量模式可能会显得不规律，特别是当它们用于突破网络审查时。通过检测这种不规律的流量模式，可以识别代理工具的存在。例如，如果网络上出现了突然增加的不规律流量，可能需要进一步调查其来源并识别是否有代理工具的参与。需要注意的是，这些方法并不是绝对可靠的，因为代理工具的不断演化可能导致它们采取更高级的措施来隐藏其流量特征。因此，为了提高识别的准确性，可能需要结合多种技术手段和算法，也需要不断更新和调整识别方法。最重要的是，监控网络流量和识别代理工具的目的应该是合法的，并且应该遵循相关的法律和政策。7总结总结来说，识别代理工具的流量特征需要多种技术手段和方法的结合，以便更精确地检测它们的存在。随着代理工具的不断演化，网络管理员和监管机构需要不断改进他们的识别技术，以应对新的挑战。识别代理工具的流量特征不仅有助于网络安全，还有助于维护网络的稳定性和合法性。 END
Linux 快速换源yum install -y wget ＃必备工具 ＃使用之前请确保已经安装wget，如未安装请执行下面一条命令来安装 ＃备份原来的源 sudo mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bk ＃下载阿里源 cd /etc/yum.repos.d wget -nc http://mirrors.aliyun.com/repo/Centos-7.repo ＃更改阿里yum源为默认源 sudo mv Centos-7.repo CentOS-Base.repo ＃更新本地yum缓存 # 全部清除 sudo yum clean all # 更新列表 sudo yum list # 缓存yum包信息到本机，提高搜索速度 sudo yum makecache网卡配置错误Cannot find a valid baseurl for repo: base/7/x86_64# 解决办法： # 修改网卡配置文件： vi /etc/sysconfig/network-scripts/ifcfg-eno16777736 # (注意：如果显示打开的是新文件，可能你的网卡的文件不叫ifcfg-eno16777736 ，有的叫ens33，你就使用命令“vi /etc/sysconfig/network-scripts/ifcfg-ens33”，来打开网卡配置文件) # 将onboot修改为“yes” #重启网络服务： service network restart #再使用安装Vim的命令： yum -y install vim* #就可以安装了。yum安装出现另一个程序被锁定Another app is currently holding the yum lock; waiting for it to exit 这句话的意思就是有一个应用程序正在锁定yum，等待退出。# 解决办法： # 1、杀死这个应用程序 #命令： kill -s 9 56722 #kill 的用法： #kill [信号代码] 进程ID #注：信号代码可以省略；我们常用的信号代码是 -s 9 ，表示强制终止； #56722是这个进程的ID # 2、强制关掉yum进程，并重新运行yum rm -f /var/run/yum.pid #注：强制删除：rm -f #解释：强制删除文件： /var/run/yum.pid Centos防火墙相关命令#永久关闭防火墙 systemctl disable firewalld #y systemctl enable firewalld #查看防火墙状态 systemctl status firewalld #关闭防火墙 systemctl stop firewalld #打开防火墙 systemctl start firewalldCentos查看自启动服务#查看自启动的服务 systemctl list-unit-filesCentos hostname相关命令#查看当前主机名 hostname #方法一：命令永久修改主机名 hostnamectl set-hostname 主机名 #方法二 通过修改文件 #修改文件 vi /etc/hostname #添加主机名 Slave01 vi /etc/sysconfig/network #添加IP和hostname对应关系 10.238.162.33 Slave01 #重启reboot生效 #方法三 临时修改主机名 hostname Slave01
报告以邮件发送到邮箱 在log下生成巡检报告。#!/bin/bash #主机信息每日巡检 IPADDR=$(ifconfig eth0|grep 'inet addr'|awk -F '[ :]' '{print $13}') #环境变量PATH没设好，在cron里执行时有很多命令会找不到 export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin source /etc/profile [ $(id -u) -gt 0 ] && echo "请用root用户执行此脚本！" && exit 1 centosVersion=$(awk '{print $(NF-1)}' /etc/redhat-release) VERSION="2020-03-16" #日志相关 PROGPATH=`echo $0 | sed -e 's,[\\/][^\\/][^\\/]*$,,'` [ -f $PROGPATH ] && PROGPATH="." LOGPATH="$PROGPATH/log" [ -e $LOGPATH ] || mkdir $LOGPATH RESULTFILE="$LOGPATH/HostDailyCheck-$IPADDR-`date +%Y%m%d`.txt" #定义报表的全局变量 report_DateTime="" #日期 ok report_Hostname="" #主机名 ok report_OSRelease="" #发行版本 ok report_Kernel="" #内核 ok report_Language="" #语言/编码 ok report_LastReboot="" #最近启动时间 ok report_Uptime="" #运行时间（天） ok report_CPUs="" #CPU数量 ok report_CPUType="" #CPU类型 ok report_Arch="" #CPU架构 ok report_MemTotal="" #内存总容量(MB) ok report_MemFree="" #内存剩余(MB) ok report_MemUsedPercent="" #内存使用率% ok report_DiskTotal="" #硬盘总容量(GB) ok report_DiskFree="" #硬盘剩余(GB) ok report_DiskUsedPercent="" #硬盘使用率% ok report_InodeTotal="" #Inode总量 ok report_InodeFree="" #Inode剩余 ok report_InodeUsedPercent="" #Inode使用率 ok report_IP="" #IP地址 ok report_MAC="" #MAC地址 ok report_Gateway="" #默认网关 ok report_DNS="" #DNS ok report_Listen="" #监听 ok report_Selinux="" #Selinux ok report_Firewall="" #防火墙 ok report_USERs="" #用户 ok report_USEREmptyPassword="" #空密码用户 ok report_USERTheSameUID="" #相同ID的用户 ok report_PasswordExpiry="" #密码过期（天） ok report_RootUser="" #root用户 ok report_Sudoers="" #sudo授权 ok report_SSHAuthorized="" #SSH信任主机 ok report_SSHDProtocolVersion="" #SSH协议版本 ok report_SSHDPermitRootLogin="" #允许root远程登录 ok report_DefunctProsess="" #僵尸进程数量 ok report_SelfInitiatedService="" #自启动服务数量 ok report_SelfInitiatedProgram="" #自启动程序数量 ok report_RuningService="" #运行中服务数 ok report_Crontab="" #计划任务数 ok report_Syslog="" #日志服务 ok report_SNMP="" #SNMP OK report_NTP="" #NTP ok report_JDK="" #JDK版本 ok function version(){ echo "" echo "" echo "系统巡检脚本：Version $VERSION" } function getCpuStatus(){ echo "" echo "" echo "############################ CPU检查 #############################" Physical_CPUs=$(grep "physical id" /proc/cpuinfo| sort | uniq | wc -l) Virt_CPUs=$(grep "processor" /proc/cpuinfo | wc -l) CPU_Kernels=$(grep "cores" /proc/cpuinfo|uniq| awk -F ': ' '{print $2}') CPU_Type=$(grep "model name" /proc/cpuinfo | awk -F ': ' '{print $2}' | sort | uniq) CPU_Arch=$(uname -m) echo "物理CPU个数:$Physical_CPUs" echo "逻辑CPU个数:$Virt_CPUs" echo "每CPU核心数:$CPU_Kernels" echo " CPU型号:$CPU_Type" echo " CPU架构:$CPU_Arch" #报表信息 report_CPUs=$Virt_CPUs #CPU数量 report_CPUType=$CPU_Type #CPU类型 report_Arch=$CPU_Arch #CPU架构 } function getMemStatus(){ echo "" echo "" echo "############################ 内存检查 ############################" if [[ $centosVersion < 7 ]];then free -mo else free -h fi #报表信息 MemTotal=$(grep MemTotal /proc/meminfo| awk '{print $2}') #KB MemFree=$(grep MemFree /proc/meminfo| awk '{print $2}') #KB let MemUsed=MemTotal-MemFree MemPercent=$(awk "BEGIN {if($MemTotal==0){printf 100}else{printf \"%.2f\",$MemUsed*100/$MemTotal}}") report_MemTotal="$((MemTotal/1024))""MB" #内存总容量(MB) report_MemFree="$((MemFree/1024))""MB" #内存剩余(MB) report_MemUsedPercent="$(awk "BEGIN {if($MemTotal==0){printf 100}else{printf \"%.2f\",$MemUsed*100/$MemTotal}}")""%" #内存使用率% } function getDiskStatus(){ echo "" echo "" echo "############################ 磁盘检查 ############################" df -hiP | sed 's/Mounted on/Mounted/'> /tmp/inode df -hTP | sed 's/Mounted on/Mounted/'> /tmp/disk join /tmp/disk /tmp/inode | awk '{print $1,$2,"|",$3,$4,$5,$6,"|",$8,$9,$10,$11,"|",$12}'| column -t #报表信息 diskdata=$(df -TP | sed '1d' | awk '$2!="tmpfs"{print}') #KB disktotal=$(echo "$diskdata" | awk '{total+=$3}END{print total}') #KB diskused=$(echo "$diskdata" | awk '{total+=$4}END{print total}') #KB diskfree=$((disktotal-diskused)) #KB diskusedpercent=$(echo $disktotal $diskused | awk '{if($1==0){printf 100}else{printf "%.2f",$2*100/$1}}') inodedata=$(df -iTP | sed '1d' | awk '$2!="tmpfs"{print}') inodetotal=$(echo "$inodedata" | awk '{total+=$3}END{print total}') inodeused=$(echo "$inodedata" | awk '{total+=$4}END{print total}') inodefree=$((inodetotal-inodeused)) inodeusedpercent=$(echo $inodetotal $inodeused | awk '{if($1==0){printf 100}else{printf "%.2f",$2*100/$1}}') report_DiskTotal=$((disktotal/1024/1024))"GB" #硬盘总容量(GB) report_DiskFree=$((diskfree/1024/1024))"GB" #硬盘剩余(GB) report_DiskUsedPercent="$diskusedpercent""%" #硬盘使用率% report_InodeTotal=$((inodetotal/1000))"K" #Inode总量 report_InodeFree=$((inodefree/1000))"K" #Inode剩余 report_InodeUsedPercent="$inodeusedpercent""%" #Inode使用率% } function getSystemStatus(){ echo "" echo "" echo "############################ 系统检查 ############################" if [ -e /etc/sysconfig/i18n ];then default_LANG="$(grep "LANG=" /etc/sysconfig/i18n | grep -v "^#" | awk -F '"' '{print $2}')" else default_LANG=$LANG fi export LANG="en_US.UTF-8" Release=$(cat /etc/redhat-release 2>/dev/null) Kernel=$(uname -r) OS=$(uname -o) Hostname=$(uname -n) SELinux=$(/usr/sbin/sestatus | grep "SELinux status: " | awk '{print $3}') LastReboot=$(who -b | awk '{print $3,$4}') uptime=$(uptime | sed 's/.*up \([^,]*\), .*/\1/') echo " 系统：$OS" echo " 发行版本：$Release" echo " 内核：$Kernel" echo " 主机名：$Hostname" echo " SELinux：$SELinux" echo "语言/编码：$default_LANG" echo " 当前时间：$(date +'%F %T')" echo " 最后启动：$LastReboot" echo " 运行时间：$uptime" #报表信息 report_DateTime=$(date +"%F %T") #日期 report_Hostname="$Hostname" #主机名 report_OSRelease="$Release" #发行版本 report_Kernel="$Kernel" #内核 report_Language="$default_LANG" #语言/编码 report_LastReboot="$LastReboot" #最近启动时间 report_Uptime="$uptime" #运行时间（天） report_Selinux="$SELinux" export LANG="$default_LANG" } function getServiceStatus(){ echo "" echo "" echo "############################ 服务检查 ############################" echo "" if [[ $centosVersion > 7 ]];then conf=$(systemctl list-unit-files --type=service --state=enabled --no-pager | grep "enabled") process=$(systemctl list-units --type=service --state=running --no-pager | grep ".service") #报表信息 report_SelfInitiatedService="$(echo "$conf" | wc -l)" #自启动服务数量 report_RuningService="$(echo "$process" | wc -l)" #运行中服务数量 else conf=$(/sbin/chkconfig | grep -E ":on|:启用") process=$(/sbin/service --status-all 2>/dev/null | grep -E "is running|正在运行") #报表信息 report_SelfInitiatedService="$(echo "$conf" | wc -l)" #自启动服务数量 report_RuningService="$(echo "$process" | wc -l)" #运行中服务数量 fi echo "服务配置" echo "--------" echo "$conf" | column -t echo "" echo "正在运行的服务" echo "--------------" echo "$process" } function getAutoStartStatus(){ echo "" echo "" echo "############################ 自启动检查 ##########################" conf=$(grep -v "^#" /etc/rc.d/rc.local| sed '/^$/d') echo "$conf" #报表信息 report_SelfInitiatedProgram="$(echo $conf | wc -l)" #自启动程序数量 } function getLoginStatus(){ echo "" echo "" echo "############################ 登录检查 ############################" last | head } function getNetworkStatus(){ echo "" echo "" echo "############################ 网络检查 ############################" if [[ $centosVersion < 7 ]];then /sbin/ifconfig -a | grep -v packets | grep -v collisions | grep -v inet6 else #ip a for i in $(ip link | grep BROADCAST | awk -F: '{print $2}');do ip add show $i | grep -E "BROADCAST|global"| awk '{print $2}' | tr '\n' ' ' ;echo "" ;done fi GATEWAY=$(ip route | grep default | awk '{print $3}') DNS=$(grep nameserver /etc/resolv.conf| grep -v "#" | awk '{print $2}' | tr '\n' ',' | sed 's/,$//') echo "" echo "网关：$GATEWAY " echo " DNS：$DNS" #报表信息 IP=$(ip -f inet addr | grep -v 127.0.0.1 | grep inet | awk '{print $NF,$2}' | tr '\n' ',' | sed 's/,$//') MAC=$(ip link | grep -v "LOOPBACK\|loopback" | awk '{print $2}' | sed 'N;s/\n//' | tr '\n' ',' | sed 's/,$//') report_IP="$IP" #IP地址 report_MAC=$MAC #MAC地址 report_Gateway="$GATEWAY" #默认网关 report_DNS="$DNS" #DNS } function getListenStatus(){ echo "" echo "" echo "############################ 监听检查 ############################" TCPListen=$(ss -ntul | column -t) echo "$TCPListen" #报表信息 report_Listen="$(echo "$TCPListen"| sed '1d' | awk '/tcp/ {print $5}' | awk -F: '{print $NF}' | sort | uniq | wc -l)" } function getCronStatus(){ echo "" echo "" echo "############################ 计划任务检查 ########################" Crontab=0 for shell in $(grep -v "/sbin/nologin" /etc/shells);do for user in $(grep "$shell" /etc/passwd| awk -F: '{print $1}');do crontab -l -u $user >/dev/null 2>&1 status=$? if [ $status -eq 0 ];then echo "$user" echo "--------" crontab -l -u $user let Crontab=Crontab+$(crontab -l -u $user | wc -l) echo "" fi done done #计划任务 find /etc/cron* -type f | xargs -i ls -l {} | column -t let Crontab=Crontab+$(find /etc/cron* -type f | wc -l) #报表信息 report_Crontab="$Crontab" #计划任务数 } function getHowLongAgo(){ # 计算一个时间戳离现在有多久了 datetime="$*" [ -z "$datetime" ] && echo "错误的参数：getHowLongAgo() $*" Timestamp=$(date +%s -d "$datetime") #转化为时间戳 Now_Timestamp=$(date +%s) Difference_Timestamp=$(($Now_Timestamp-$Timestamp)) days=0;hours=0;minutes=0; sec_in_day=$((60*60*24)); sec_in_hour=$((60*60)); sec_in_minute=60 while (( $(($Difference_Timestamp-$sec_in_day)) > 1 )) do let Difference_Timestamp=Difference_Timestamp-sec_in_day let days++ done while (( $(($Difference_Timestamp-$sec_in_hour)) > 1 )) do let Difference_Timestamp=Difference_Timestamp-sec_in_hour let hours++ done echo "$days 天 $hours 小时前" } function getUserLastLogin(){ # 获取用户最近一次登录的时间，含年份 # 很遗憾last命令不支持显示年份，只有"last -t YYYYMMDDHHMMSS"表示某个时间之间的登录，我 # 们只能用最笨的方法了，对比今天之前和今年元旦之前（或者去年之前和前年之前……）某个用户 # 登录次数，如果登录统计次数有变化，则说明最近一次登录是今年。 username=$1 : ${username:="`whoami`"} thisYear=$(date +%Y) oldesYear=$(last | tail -n1 | awk '{print $NF}') while(( $thisYear >= $oldesYear));do loginBeforeToday=$(last $username | grep $username | wc -l) loginBeforeNewYearsDayOfThisYear=$(last $username -t $thisYear"0101000000" | grep $username | wc -l) if [ $loginBeforeToday -eq 0 ];then echo "从未登录过" break elif [ $loginBeforeToday -gt $loginBeforeNewYearsDayOfThisYear ];then lastDateTime=$(last -i $username | head -n1 | awk '{for(i=4;i<(NF-2);i++)printf"%s ",$i}')" $thisYear" #格式如: Sat Nov 2 20:33 2015 lastDateTime=$(date "+%Y-%m-%d %H:%M:%S" -d "$lastDateTime") echo "$lastDateTime" break else thisYear=$((thisYear-1)) fi done } function getUserStatus(){ echo "" echo "" echo "############################ 用户检查 ############################" #/etc/passwd 最后修改时间 pwdfile="$(cat /etc/passwd)" Modify=$(stat /etc/passwd | grep Modify | tr '.' ' ' | awk '{print $2,$3}') echo "/etc/passwd 最后修改时间：$Modify ($(getHowLongAgo $Modify))" echo "" echo "特权用户" echo "--------" RootUser="" for user in $(echo "$pwdfile" | awk -F: '{print $1}');do if [ $(id -u $user) -eq 0 ];then echo "$user" RootUser="$RootUser,$user" fi done echo "" echo "用户列表" echo "--------" USERs=0 echo "$( echo "用户名 UID GID HOME SHELL 最后一次登录" for shell in $(grep -v "/sbin/nologin" /etc/shells);do for username in $(grep "$shell" /etc/passwd| awk -F: '{print $1}');do userLastLogin="$(getUserLastLogin $username)" echo "$pwdfile" | grep -w "$username" |grep -w "$shell"| awk -F: -v lastlogin="$(echo "$userLastLogin" | tr ' ' '_')" '{print $1,$3,$4,$6,$7,lastlogin}' done let USERs=USERs+$(echo "$pwdfile" | grep "$shell"| wc -l) done )" | column -t echo "" echo "空密码用户" echo "----------" USEREmptyPassword="" for shell in $(grep -v "/sbin/nologin" /etc/shells);do for user in $(echo "$pwdfile" | grep "$shell" | cut -d: -f1);do r=$(awk -F: '$2=="!!"{print $1}' /etc/shadow | grep -w $user) if [ ! -z $r ];then echo $r USEREmptyPassword="$USEREmptyPassword,"$r fi done done echo "" echo "相同ID的用户" echo "------------" USERTheSameUID="" UIDs=$(cut -d: -f3 /etc/passwd | sort | uniq -c | awk '$1>1{print $2}') for uid in $UIDs;do echo -n "$uid"; USERTheSameUID="$uid" r=$(awk -F: 'ORS="";$3=='"$uid"'{print ":",$1}' /etc/passwd) echo "$r" echo "" USERTheSameUID="$USERTheSameUID $r," done #报表信息 report_USERs="$USERs" #用户 report_USEREmptyPassword=$(echo $USEREmptyPassword | sed 's/^,//') report_USERTheSameUID=$(echo $USERTheSameUID | sed 's/,$//') report_RootUser=$(echo $RootUser | sed 's/^,//') #特权用户 } function getPasswordStatus { echo "" echo "" echo "############################ 密码检查 ############################" pwdfile="$(cat /etc/passwd)" echo "" echo "密码过期检查" echo "------------" result="" for shell in $(grep -v "/sbin/nologin" /etc/shells);do for user in $(echo "$pwdfile" | grep "$shell" | cut -d: -f1);do get_expiry_date=$(/usr/bin/chage -l $user | grep 'Password expires' | cut -d: -f2) if | $get_expiry_date = 'never' ;then printf "%-15s 永不过期\n" $user result="$result,$user:never" else password_expiry_date=$(date -d "$get_expiry_date" "+%s") current_date=$(date "+%s") diff=$(($password_expiry_date-$current_date)) let DAYS=$(($diff/(60*60*24))) printf "%-15s %s天后过期\n" $user $DAYS result="$result,$user:$DAYS days" fi done done report_PasswordExpiry=$(echo $result | sed 's/^,//') echo "" echo "密码策略检查" echo "------------" grep -v "#" /etc/login.defs | grep -E "PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_MIN_LEN|PASS_WARN_AGE" } function getSudoersStatus(){ echo "" echo "" echo "############################ Sudoers检查 #########################" conf=$(grep -v "^#" /etc/sudoers| grep -v "^Defaults" | sed '/^$/d') echo "$conf" echo "" #报表信息 report_Sudoers="$(echo $conf | wc -l)" } function getInstalledStatus(){ echo "" echo "" echo "############################ 软件检查 ############################" rpm -qa --last | head | column -t } function getProcessStatus(){ echo "" echo "" echo "############################ 进程检查 ############################" if [ $(ps -ef | grep defunct | grep -v grep | wc -l) -ge 1 ];then echo "" echo "僵尸进程"; echo "--------" ps -ef | head -n1 ps -ef | grep defunct | grep -v grep fi echo "" echo "内存占用TOP10" echo "-------------" echo -e "PID %MEM RSS COMMAND $(ps aux | awk '{print $2, $4, $6, $11}' | sort -k3rn | head -n 10 )"| column -t echo "" echo "CPU占用TOP10" echo "------------" top b -n1 | head -17 | tail -11 #报表信息 report_DefunctProsess="$(ps -ef | grep defunct | grep -v grep|wc -l)" } function getJDKStatus(){ echo "" echo "" echo "############################ JDK检查 #############################" java -version 2>/dev/null if [ $? -eq 0 ];then java -version 2>&1 fi echo "JAVA_HOME=\"$JAVA_HOME\"" #报表信息 report_JDK="$(java -version 2>&1 | grep version | awk '{print $1,$3}' | tr -d '"')" } function getSyslogStatus(){ echo "" echo "" echo "############################ syslog检查 ##########################" echo "服务状态：$(getState rsyslog)" echo "" echo "/etc/rsyslog.conf" echo "-----------------" cat /etc/rsyslog.conf 2>/dev/null | grep -v "^#" | grep -v "^\\$" | sed '/^$/d' | column -t #报表信息 report_Syslog="$(getState rsyslog)" } function getFirewallStatus(){ echo "" echo "" echo "############################ 防火墙检查 ##########################" #防火墙状态，策略等 if [[ $centosVersion < 7 ]];then /etc/init.d/iptables status >/dev/null 2>&1 status=$? if [ $status -eq 0 ];then s="active" elif [ $status -eq 3 ];then s="inactive" elif [ $status -eq 4 ];then s="permission denied" else s="unknown" fi else s="$(getState iptables)" fi echo "iptables: $s" echo "" echo "/etc/sysconfig/iptables" echo "-----------------------" cat /etc/sysconfig/iptables 2>/dev/null #报表信息 report_Firewall="$s" } function getSNMPStatus(){ #SNMP服务状态，配置等 echo "" echo "" echo "############################ SNMP检查 ############################" status="$(getState snmpd)" echo "服务状态：$status" echo "" if [ -e /etc/snmp/snmpd.conf ];then echo "/etc/snmp/snmpd.conf" echo "--------------------" cat /etc/snmp/snmpd.conf 2>/dev/null | grep -v "^#" | sed '/^$/d' fi #报表信息 report_SNMP="$(getState snmpd)" } function getState(){ if [[ $centosVersion < 7 ]];then if [ -e "/etc/init.d/$1" ];then if [ `/etc/init.d/$1 status 2>/dev/null | grep -E "is running|正在运行" | wc -l` -ge 1 ];then r="active" else r="inactive" fi else r="unknown" fi else #CentOS 7+ r="$(systemctl is-active $1 2>&1)" fi echo "$r" } function getSSHStatus(){ #SSHD服务状态，配置,受信任主机等 echo "" echo "" echo "############################ SSH检查 #############################" #检查受信任主机 pwdfile="$(cat /etc/passwd)" echo "服务状态：$(getState sshd)" Protocol_Version=$(cat /etc/ssh/sshd_config | grep Protocol | awk '{print $2}') echo "SSH协议版本：$Protocol_Version" echo "" echo "信任主机" echo "--------" authorized=0 for user in $(echo "$pwdfile" | grep /bin/bash | awk -F: '{print $1}');do authorize_file=$(echo "$pwdfile" | grep -w $user | awk -F: '{printf $6"/.ssh/authorized_keys"}') authorized_host=$(cat $authorize_file 2>/dev/null | awk '{print $3}' | tr '\n' ',' | sed 's/,$//') if [ ! -z $authorized_host ];then echo "$user 授权 \"$authorized_host\" 无密码访问" fi let authorized=authorized+$(cat $authorize_file 2>/dev/null | awk '{print $3}'|wc -l) done echo "" echo "是否允许ROOT远程登录" echo "--------------------" config=$(cat /etc/ssh/sshd_config | grep PermitRootLogin) firstChar=${config:0:1} if [ $firstChar == "#" ];then PermitRootLogin="yes" #默认是允许ROOT远程登录的 else PermitRootLogin=$(echo $config | awk '{print $2}') fi echo "PermitRootLogin $PermitRootLogin" echo "" echo "/etc/ssh/sshd_config" echo "--------------------" cat /etc/ssh/sshd_config | grep -v "^#" | sed '/^$/d' #报表信息 report_SSHAuthorized="$authorized" #SSH信任主机 report_SSHDProtocolVersion="$Protocol_Version" #SSH协议版本 report_SSHDPermitRootLogin="$PermitRootLogin" #允许root远程登录 } function getNTPStatus(){ #NTP服务状态，当前时间，配置等 echo "" echo "" echo "############################ NTP检查 #############################" if [ -e /etc/ntp.conf ];then echo "服务状态：$(getState ntpd)" echo "" echo "/etc/ntp.conf" echo "-------------" cat /etc/ntp.conf 2>/dev/null | grep -v "^#" | sed '/^$/d' fi #报表信息 report_NTP="$(getState ntpd)" } function uploadHostDailyCheckReport(){ json="{ \"DateTime\":\"$report_DateTime\", \"Hostname\":\"$report_Hostname\", \"OSRelease\":\"$report_OSRelease\", \"Kernel\":\"$report_Kernel\", \"Language\":\"$report_Language\", \"LastReboot\":\"$report_LastReboot\", \"Uptime\":\"$report_Uptime\", \"CPUs\":\"$report_CPUs\", \"CPUType\":\"$report_CPUType\", \"Arch\":\"$report_Arch\", \"MemTotal\":\"$report_MemTotal\", \"MemFree\":\"$report_MemFree\", \"MemUsedPercent\":\"$report_MemUsedPercent\", \"DiskTotal\":\"$report_DiskTotal\", \"DiskFree\":\"$report_DiskFree\", \"DiskUsedPercent\":\"$report_DiskUsedPercent\", \"InodeTotal\":\"$report_InodeTotal\", \"InodeFree\":\"$report_InodeFree\", \"InodeUsedPercent\":\"$report_InodeUsedPercent\", \"IP\":\"$report_IP\", \"MAC\":\"$report_MAC\", \"Gateway\":\"$report_Gateway\", \"DNS\":\"$report_DNS\", \"Listen\":\"$report_Listen\", \"Selinux\":\"$report_Selinux\", \"Firewall\":\"$report_Firewall\", \"USERs\":\"$report_USERs\", \"USEREmptyPassword\":\"$report_USEREmptyPassword\", \"USERTheSameUID\":\"$report_USERTheSameUID\", \"PasswordExpiry\":\"$report_PasswordExpiry\", \"RootUser\":\"$report_RootUser\", \"Sudoers\":\"$report_Sudoers\", \"SSHAuthorized\":\"$report_SSHAuthorized\", \"SSHDProtocolVersion\":\"$report_SSHDProtocolVersion\", \"SSHDPermitRootLogin\":\"$report_SSHDPermitRootLogin\", \"DefunctProsess\":\"$report_DefunctProsess\", \"SelfInitiatedService\":\"$report_SelfInitiatedService\", \"SelfInitiatedProgram\":\"$report_SelfInitiatedProgram\", \"RuningService\":\"$report_RuningService\", \"Crontab\":\"$report_Crontab\", \"Syslog\":\"$report_Syslog\", \"SNMP\":\"$report_SNMP\", \"NTP\":\"$report_NTP\", \"JDK\":\"$report_JDK\" }" #echo "$json" curl -l -H "Content-type: application/json" -X POST -d "$json" "$uploadHostDailyCheckReportApi" 2>/dev/null } function getchage_file_24h() { echo "############################ 文件检查 #############################" check2=$(find / -name '*.sh' -mtime -1) check21=$(find / -name '*.asp' -mtime -1) check22=$(find / -name '*.php' -mtime -1) check23=$(find / -name '*.aspx' -mtime -1) check24=$(find / -name '*.jsp' -mtime -1) check25=$(find / -name '*.html' -mtime -1) check26=$(find / -name '*.htm' -mtime -1) check9=$(find / -name core -exec ls -l {} \;) check10=$(cat /etc/crontab) check12=$(ls -alt /usr/bin | head -10) cat <<EOF ############################查看所有被修改过的文件返回最近24小时内的############################ ${check2} ${check21} ${check22} ${check23} ${check24} ${check25} ${check26} ${line} ############################检查定时文件的完整性############################ ${check10} ${line} ############################查看系统命令是否被替换############################ ${check12} ${line} EOF } function check(){ version getSystemStatus getCpuStatus getMemStatus getDiskStatus getNetworkStatus getListenStatus getProcessStatus getServiceStatus getAutoStartStatus getLoginStatus getCronStatus getUserStatus getPasswordStatus getSudoersStatus getJDKStatus getFirewallStatus getSSHStatus getSyslogStatus getSNMPStatus getNTPStatus getInstalledStatus getchage_file_24h } #执行检查并保存检查结果 check > $RESULTFILE echo "检查结果：$RESULTFILE" echo -e "`date "+%Y-%m-%d %H:%M:%S"` 阿里云PHP企业平台巡检报告" | mail -a $RESULTFILE -s "阿里云PHP企业平台巡检报告" h@163.com END