去年帮一个学弟看他的态网PHP作业,打开压缩包一看,站开三个文件:index.php、发作conn.php、业代login.php。态网数据库里一张user表,站开字段就四个——id、发作username、业代password、态网email。站开代码能跑,发作登录能过,业代但密码是态网明文存的。我跟他说,站开你这作业交上去老师可能给过,发作但放到网上活不过三天。
PHP动态网站开发这门课,作业代码往往长一个样:一个连接数据库的公共文件,几个增删改查的页面,再加一个简陋的登录注册。结构没错,问题出在细节。下面拿几个典型作业场景,直接说代码怎么写更靠谱。
很多作业里conn.php是这样的:
$conn = mysqli_connect("localhost", "root", "123456", "test"); if (!$conn) { die("连接失败"); } 能连上,但硬编码密码这事,养成习惯就麻烦了。改进方式也简单,用一个config.php返回数组,conn.php去读。作业里老师不一定扣分,但你自己得知道真实项目里没人这么干。另外mysqli_connect的报错信息别直接输出到页面,写进日志就行,不然数据库地址、用户名全暴露了。
作业里最常见的登录逻辑是拼SQL:
$sql = "SELECT * FROM user WHERE username='$username' AND password='$password'"; 这行代码在课堂上讲SQL注入时老师肯定演示过,输入 ' OR '1'='1 就能直接进后台。用mysqli的prepare方法改写:
$stmt = $conn->prepare("SELECT id, password FROM user WHERE username = ?"); $stmt->bind_param("s", $username); $stmt->execute(); $result = $stmt->get_result(); 取到用户后再用password_verify()比对密码。注册的时候用password_hash()存哈希值。这两个函数PHP 5.5以后自带,不需要额外库。作业代码里加上这两步,安全性直接上一个台阶。
列表页作业通常直接 SELECT * FROM article,数据一多页面就卡。分页的核心就两个参数:当前页码和每页条数。用LIMIT offset, count实现:
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1; $size = 10; $offset = ($page - 1) * $size; $sql = "SELECT id, title, created_at FROM article ORDER BY id DESC LIMIT ?, ?"; 注意LIMIT后面的参数也得用bind_param绑定,类型是"ii"。总页数用 SELECT COUNT(*) 单独查一次。别在循环里查总数,那是新手常犯的错。
作业里上传头像或附件的代码,很多只判断了 $_FILES['file']['type']。这个值来自浏览器,可以伪造。更可靠的做法是用finfo_open()读文件的真实MIME类型,再配合白名单:
$finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $_FILES['file']['tmp_name']); $allow = ['image/jpeg', 'image/png', 'image/gif']; if (!in_array($mime, $allow)) { exit('不允许的文件类型'); } 保存文件时用uniqid()或md5(时间戳)重命名,别直接用用户上传的文件名。上传目录的权限设成755,别给写权限之外的执行权限。
session_start()之前不能有任何输出,包括空格和BOM头。作业里经常在文件开头写了注释然后空一行再写 <?php,结果session就是启动不了。检查方法很简单:在session_start()后面加 var_dump(headers_sent());,返回true就说明前面有输出。另外登录成功后调用session_regenerate_id(true),防止会话固定攻击。
写完作业代码,自己拿sqlmap扫一下登录框,用Burp抓一下上传请求,比对着课本检查十遍都管用。PHP入门容易,但把安全习惯从一开始就养好,后面能省很多事。