由于经济上的原因需要低成本的维护这个博客程序,在万能的某宝上找了几个低价的虚拟主机进行对比,最终选了一款18块钱一年的虚拟主机(手动狗头)。结合域名续费费用的39元,算下来运营的费用来到了57块钱。对一个来自大山深处的贫困中年人来说也是一笔非常大的巨款,所以各位老板有没有看到本文的赞助按钮….(再次手动狗头)。但好景不长由于这个一分钱一分货的原因很快就遇到了问题:时不时就发现本站被挂上了黄网。联系虚拟主机客服后得到的回复是:"你这个虚拟主机维护都不够人工费用的,你换一个就会好很多"。没办法,2025每个人都有每个人的难处,唉。
但是对于黑客利用漏洞挂黄网来这种行为对于深处中国大陆农村大山深处的贫困四有青年来说这怎你能忍?于是就有了这个文章对ftp上的index.php进行检查。
思路如下:
1.利用commons-net:commons-net:3.11.1
工具进行FTP链接获取文件列表。
2.检查FTP端的index.php文件是否存在,如果不存在则上传本地inde.php进行替换。
3.获取FTP端的index.php文件大小与本地index.php文件大小做对比,如果发现文件大小不一致则上传本地inde.php进行替换。
4.打包这个程序,使其定时运行在低功耗的本地小主机上。
java文件
package top.foxhome.webkeep;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import java.io.*;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Date;
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Main {
public static final String FTP_ADDRESS = "ftp.xxxxx.xxxx";
public static final int FTP_PORT = 21;
public static final String FTP_USER_NAME = "foxhome_top";
public static final String FTP_USER_PWD = "123456";
public static File logFile;
public static void main(String[] args) {
//本地的php文件
File localIndexFile = new File(getJarPath(), "index.php");
logFile = new File(getJarPath(), "webkeep.log");
if (!localIndexFile.exists()) {
System.out.println("文件:" + localIndexFile + "不存在!!");
appendLog("文件:" + localIndexFile + "不存在!!", logFile);
return;
}
FTPClient ftpClient = getFTPClient(FTP_ADDRESS, FTP_USER_NAME, FTP_USER_PWD, FTP_PORT);
ftpClient.setControlEncoding("UTF-8"); // 中文支持
try {
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory("/");
//获取 / 目录下的文件列表
FTPFile[] mFTPFiles = ftpClient.listFiles();
FTPFile indexFTPFile = null;
//迭代FTP目录文件找出index.php
for (int i = 0; i < mFTPFiles.length; i++) {
FTPFile ftpfile = mFTPFiles[i];
//检查"index.php是否存在"
if ("index.php".equals(ftpfile.getName()))
indexFTPFile = ftpfile;
}
/**
* 处理两种index.php文件遇到的情况 1.index.php被恶意删除 2.index.php被篡改
* 如果出现了这两种情况则将本地的index.php文件进行上传替换
*/
//如果没有找到index文件则上传
if (indexFTPFile == null) {
System.out.println("服务端没有index.php文件");
appendLog("服务端没有index.php文件", logFile);
uploadFile(localIndexFile, ftpClient, "/");
} else {
//检查篡改
long ftpFileSize = indexFTPFile.getSize();
long localFileSize = localIndexFile.length();
System.out.println("ftpFileSize:" + ftpFileSize + " localFileSize:" + localFileSize);
//文件被篡改了进行删除和替换
if (ftpFileSize != localFileSize) {
System.out.println("服务端文件大小与本地不一致");
appendLog("服务端文件大小与本地不一致!" + "ftpFileSize:" + ftpFileSize + " localFileSize:" + localFileSize, logFile);
uploadFile(localIndexFile, ftpClient, "/");
}
}
ftpClient.logout();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取jar文件所在的文件目录
* @return 返回文件路径
*/
public static final String getJarPath() {
String osName = System.getProperty("os.name");
/**
* 目前不知道如何处理Linux的情况,先传固定路径
*/
if (osName.equals("Linux")) {
return "/usr/local/bin/webkeep";
}
/** * 方法一:获取当前可执行jar包所在目录 */
String filePath = System.getProperty("java.class.path");
String pathSplit = System.getProperty("path.separator");//得到当前操作系统的分隔符,windows下是";",linux下是":"
/** * 若没有其他依赖,则filePath的结果应当是该可运行jar包的绝对路径, * 此时我们只需要经过字符串解析,便可得到jar所在目录 */
if (filePath.contains(pathSplit)) {
filePath = filePath.substring(0, filePath.indexOf(pathSplit));
} else if (filePath.endsWith(".jar")) {
//截取路径中的jar包名,可执行jar包运行的结果里包含".jar"
filePath = filePath.substring(0, filePath.lastIndexOf(File.separator) + 1);
}
return filePath;
}
/**
* 上传FTP文件
* @param localFile
* @param ftpClient
* @param ftpFilePath
* @return
*/
public static boolean uploadFile(File localFile, FTPClient ftpClient, String ftpFilePath) {
try {
ftpClient.changeWorkingDirectory(ftpFilePath);
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
InputStream input = new FileInputStream(localFile);
ftpClient.storeFile(localFile.getName(), input);
input.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
/**
* 构建FTP客户端
* @param ftpHost
* @param ftpUserName
* @param ftpPassword
* @param ftpPort
* @return
*/
public static FTPClient getFTPClient(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort) {
FTPClient ftpClient = null;
try {
//创建一个ftp客户端
ftpClient = new FTPClient();
// 连接FTP服务器
ftpClient.connect(ftpHost, ftpPort);
// 登陆FTP服务器
ftpClient.login(ftpUserName, ftpPassword);
if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
System.out.println("未连接到FTP,用户名或密码错误。");
appendLog("未连接到FTP,用户名或密码错误。", logFile);
ftpClient.disconnect();
} else {
System.out.println("FTP连接成功。");
}
} catch (SocketException e) {
e.printStackTrace();
System.out.println("FTP的IP地址可能错误,请正确配置。");
appendLog("FTP的IP地址可能错误,请正确配置。", logFile);
} catch (IOException e) {
e.printStackTrace();
System.out.println("FTP的端口错误,请正确配置。");
appendLog("FTP的端口错误,请正确配置。", logFile);
}
return ftpClient;
}
/**
* 添加log日志内容
* @param log
* @param logfile
*/
public static void appendLog(String log, File logfile) {
Date currentDate = new Date();
// 定义日期格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 格式化日期
String formattedDate = dateFormat.format(currentDate);
log = formattedDate + " " + log;
try (PrintWriter pw = new PrintWriter(new FileWriter(logfile, true))) {
pw.println(log); // 自动添加换行
} catch (IOException e) {
e.printStackTrace();
}
}
}
build.gradle.kts文件
plugins {
id("java")
}
group = "top.foxhome.webkeep"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
implementation("commons-net:commons-net:3.11.1")
}
tasks.test {
useJUnitPlatform()
}
/*配置打包的入口函数*/
tasks.jar {
manifest {
attributes(
"Main-Class" to "top.foxhome.webkeep.Main",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
)
}
/*将第三方jar打包也打包进产物*/
// 包含所有运行时依赖
from(configurations.runtimeClasspath.get().map {
if (it.isDirectory) it else zipTree(it)
})
// 处理重复文件冲突
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
被生成jar文件和用于对比的index.php文件上传到armbian小主机的/local/bin/webkeep
目录上
root@armbian:/usr/local/bin/webkeep# ls
index.php webkeep-1.0-SNAPSHOT.jar
赋予webkeep-1.0-SNAPSHOT.jar
755权限.
然后配置定时任务
sudo crontab -e
添加一行
*/30 * * * * /usr/bin/java -jar /usr/local/bin/webkeep/webkeep-1.0-SNAPSHOT.jar
即每三十分钟检查一次。
经过几天的运行终于成功拦截了两次篡改
root@armbian:/usr/local/bin/webkeep# ls
index.php webkeep-1.0-SNAPSHOT.jar webkeep.log webkeep.sh
root@armbian:/usr/local/bin/webkeep# cat webkeep.log
2025-05-30 12:30:07 服务端文件大小与本地不一致!ftpFileSize:51141 localFileSize:405
2025-06-02 10:30:06 服务端文件大小与本地不一致!ftpFileSize:51141 localFileSize:405
root@armbian:/usr/local/bin/webkeep#
总之,在选购商品或者服务上,便宜总是没好货,好货也总是不便宜。