改个需求建站公司拖一周,后台数据泄露还要你赔钱?别急着骂人,先看看你的站是不是裸奔。
很多设计师转前端,或者刚接私单的朋友,最头疼的不是代码写不出来,而是上线后半夜被黑客打穿。你以为只是换个图片、改个按钮的事,结果客户一问,后台账号密码全没了,SSL证书也过期了。这时候你才意识到,从零搭建一个网站,安全不是上线后补的课,而是地基。
我干了十年建站,见过太多因为忽视基础安全,导致项目黄掉的案例。今天不聊虚的,就针对咱们网站建设客户最常遇到的坑,拆解一下怎么从技术层面把风险掐灭在摇篮里。特别是那些刚入行的朋友,这篇内容能帮你省掉几十万的试错成本。
别觉得只有大公司才被黑客盯上。现在的自动化攻击脚本,每天扫描全球几十万个IP,只要发现你有漏洞,毫秒级就能注入恶意代码。
对于网站建设客户来说,最常见的威胁场景有三类:
第一类是SQL注入。 很多模板建站或者简易CMS,表单提交时没有做严格的过滤。用户只要输入一段特殊的SQL语句,比如 1' OR 1=1 --,数据库里的用户表、订单表就全暴露了。我见过一个电商客户,因为后台登录框没做限制,被刷了十万条垃圾数据,服务器直接卡死,客户差点把合同撕了。
第二类是文件上传漏洞。 设计师喜欢做拖拽上传,前端校验一下后缀名就完了。结果黑客上传一个 shell.php.jpg,通过二次请求改成 .php,直接拿到了服务器Shell权限。这时候,你的网站已经不是你的了,而是黑客的跳板。
第三类是SSL证书配置错误。 很多客户为了省钱,用免费证书,或者配置了HSTS但忘记设置过期时间。结果浏览器一直提示“连接不安全”,转化率直接腰斩。更严重的是,如果证书链不完整,中间人攻击(MITM)就能拦截用户的Cookie,窃取登录状态。
这些场景,90%都源于基础架构的疏忽。你以为你在做前端交互,其实你在裸奔。
咱们不用讲太深的底层原理,就讲几个最致命的点,让你知道为什么要改。
1. 参数化查询缺失 很多前端转后端的朋友,习惯直接拼接SQL字符串。
// 错误写法:极度危险
const query = `SELECT * FROM users WHERE id = ${userId}`;
db.query(query, (err, results) => { ... });
当 userId 是 1; DROP TABLE users; 时,你的用户表就没了。
2. 文件上传只查后缀
前端校验 file.name.endsWith('.jpg') 是防君子不防小人。黑客可以用Burp Suite抓包,把Content-Type改成 image/jpeg,文件名改成 test.php。如果后端没做白名单校验和重命名,这个文件就能执行。
3. CORS配置过宽
为了跨域方便,很多开发者直接写 Access-Control-Allow-Origin: *。这意味着任何网站都能请求你的API接口。如果接口涉及用户信息,隔壁站的JS就能直接拿到你的Token。
4. SSL证书链不完整 很多服务器只配置了叶子证书,没配中间证书。浏览器验证失败,就会降级或不安全。阿里云官方文档里明确指出,必须配置完整的证书链,包括根证书和中间证书,否则会导致部分旧版浏览器兼容性问题,甚至被标记为不安全。
这些漏洞,不是因为你技术不行,而是因为你太相信“前端校验”和“默认配置”。安全是纵深防御,每一层都不能省。
光说原理没用,直接上代码。以下是针对Node.js + Express + MySQL的示例,其他语言逻辑类似。
错误代码:
app.get('/user/:id', (req, res) => {const id = req.params.id;// 危险!直接拼接const sql = `SELECT * FROM users WHERE id = ${id}`;db.query(sql, (err, result) => {if (err) throw err;res.json(result);});
});
修复代码:
app.get('/user/:id', (req, res) => {const id = req.params.id;// 安全!使用占位符const sql = `SELECT * FROM users WHERE id = ?`;db.query(sql, [id], (err, result) => {if (err) throw err;res.json(result);});
});
关键点: 永远不要拼接用户输入到SQL中。使用驱动自带的参数绑定功能,如 ? 或 $1。
错误代码:
app.post('/upload', (req, res) => {const file = req.files.file;// 危险!只查后缀,且直接存到web目录file.mv(`./uploads/${file.name}`, (err) => {res.send('OK');});
});
修复代码:
const multer = require('multer');
const crypto = require('crypto');
const path = require('path');// 1. 存储到非Web目录,或通过Nginx限制执行
const storage = multer.diskStorage({destination: (req, file, cb) => cb(null, './private_uploads/'),filename: (req, file, cb) => {// 2. 重命名,防止文件名被利用const uniqueName = crypto.randomBytes(16).toString('hex');// 3. 白名单校验const ext = path.extname(file.originalname).toLowerCase();if (!['.jpg', '.png', '.gif'].includes(ext)) {return cb(new Error('Invalid file type'));}cb(null, uniqueName + ext);}
});const upload = multer({ storage: storage,limits: { fileSize: 5 * 1024 * 1024 } // 限制5MB
});app.post('/upload', upload.single('file'), (req, res) => {if (!req.file) return res.status(400).send('No file');res.json({ url: `/private_uploads/${req.file.filename}` });
});
关键点:
750,所有者为 www-data,其他用户无权限。location /private_uploads/ {deny all;# 或者通过PHP-FPM/Apache配置,禁止解析该目录下的.php文件
}
错误代码:
app.use((req, res, next) => {res.setHeader('Access-Control-Allow-Origin', '*');res.setHeader('Access-Control-Allow-Credentials', 'true'); // 冲突!next();
});
修复代码:
const cors = require('cors');app.use(cors({origin: ['https://yourdomain.com', 'https://admin.yourdomain.com'],credentials: true,methods: ['GET', 'POST'],allowedHeaders: ['Content-Type', 'Authorization']
}));// 添加安全头
app.use((req, res, next) => {res.setHeader('X-Content-Type-Options', 'nosniff');res.setHeader('X-Frame-Options', 'DENY');res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');res.setHeader('Content-Security-Policy', "default-src 'self'");next();
});
关键点: Access-Control-Allow-Origin 不能与 Access-Control-Allow-Credentials: true 同时使用 *。必须指定具体域名。
在Nginx中配置SSL时,必须将证书和中间证书合并为一个文件。
错误配置:
ssl_certificate /etc/nginx/certs/example.com.crt; # 只有叶子证书
ssl_certificate_key /etc/nginx/certs/example.com.key;
修复配置:
ssl_certificate /etc/nginx/certs/fullchain.pem; # 包含叶子证书+中间证书
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
关键点: 使用 openssl s_client -connect yourdomain.com:443 -showcerts 命令检查证书链是否完整。阿里云官方文档建议在申请证书时,选择“包含中间证书”的下载选项,或使用 cat leaf.crt intermediate.crt > fullchain.pem 合并。
代码写完,别急着上线。按这个清单过一遍,能挡掉80%的低级攻击。
ssllabs.com 输入你的域名,检查SSL配置得分。A+分是底线。.git、.env、phpinfo.php 等文件是否可访问。/../../etc/passwd,看是否返回403。HttpOnly、Secure、SameSite。修复流程:
最后,给一份网站建设客户可以直接用的加固清单。这不是可选的,是必须的。
unattended-upgrades(Debian/Ubuntu)或yum-cron(CentOS),自动打安全补丁。npm audit 或 composer audit,修复依赖包漏洞。express-rate-limit或Nginx limit_req,防止暴力破解和DDoS。特别注意: 很多客户问“我的站很贵,应该很安全吧?”错。安全不取决于价格,而取决于流程。再贵的服务器,如果代码有漏洞,一样被黑。再便宜的VPS,如果加固到位,也能扛住大多数攻击。
建站花了多少钱?留言说说真实价格,顺便讲讲你踩过最大的安全坑是什么。咱们互相避坑,别让客户再因为安全问题找你赔钱。