ระบบ login เป็นจุดแรกที่ hacker จะพยายามใช้ sql injection เพื่อ by pass user หรือ password การ by pass ก็คือ การไม่ต้องรู้ว่า user หรือ password จริงๆของเค้าคือ อะไร ซึ่ง php เวอร์ชั่น ใหม่ก็มี การบังคับใช้ mysqli เพื่อความปลอดภัย แต่เชื่อหรือไม่ว่าก็ยังมีคนโดนเจาะระบบอยู่ดี เป็นเพราะอะไร วันนี้เราจะมาดู case ตัวอย่าง 1 ตัวอย่าง
ถ้าคุณใช้ mysqli แต่คุณยังใช้วิธีตรวจสอบระบบ login ด้วยคำสั่ง sql แบบนี้ $sql = ” SELECT * FROM user WHERE u_user = ‘”. $user.”‘ AND u_pass = ‘”.$pass.”‘”; ต่อให้เป็น mysqli ก็ช่วยอะไรคุณไม่ได้ หรือต่อให้คุณ encrypt password ขั้นเทพขนาดไหน ก็ โดนเจาะ อยู่ดี เพราะอะไรนั่นหรอ เราลองมาดูกัน ว่าถ้า hacker ใส่ค่า user เป็น ‘ or 1=1 — แล้ว password เป็น อะไรก็ได้ จะเกิดอะไรขึ้นกับ sql syntax ของเรา
กรอก user = ‘ or 1=1 — pass = อะไรก็ได้
SELECT * FROM user WHERE u_user = ‘‘ or 1=1 — ‘ AND u_pass = ‘อะไรก็ได้’
ตรงนี้จะเห็นว่า เครื่องหมาย ขีดๆ — นี้จะทำให้ข้อความที่อยู่ ข้างหลังเป็น comment ของภาษา sql ใน mysql ดังนั้นส่วนที่เหลือ จริงๆ ก็จะเป็น SELECT * FROM user WHERE u_user = ” or 1=1 ซึ่งหมายความว่าเราก็จะได้ user ทั้งหมด ของตารางนั้น มา แต่ login เราต้องการแค่ user เดียว (โดยปกติ ) ก็ให้ลองใส่ ‘ or 1=1 UNION SELECT * FROM user LIMIT 0, 1 — หรือ admin ‘ or 1=1 — (ในกรณีที่เรารู้ว่ามี user ชื่อ admin ในระบบ ) ซึ่งจะได้ผลลัพธ์แค่ 1 แถว
คราวนี้เราจะแก้ยังไงใช้ mysqli แล้วทำไม่ยังแก้ไม่ได้ คำตอบที่ผมจะเสนอวิธีแก้ในวันนี้ ก็คือ จงใช้ myqli + prepare + bind_param ซะ แล้วชีวิตจะดีขึ้น ลองดูตัวอย่าง code
login.php
<?php include "config.php"; $isSqlInject = true; ?> <form role="form" method="post" action="login.php" style="magin:20px;" > <fieldset> <h5 style="color: red;"></h5> <div > <input class="form-control" placeholder="user" name="user" type="user" autofocus="" style="magin:20px;"> </div> <div style="magin:20px;"> <input type="password" class="form-control" name="pass" style="magin:20px;"> </div> <p> <input type="submit" value="Login" style="magin:20px;"> </p> </fieldset> </form> <?php if(isset($_POST['user'])){ $user = isset($_POST['user'])?$_POST['user']:""; $pass = isset($_POST['pass'])?$_POST['pass']:""; $pass = md5($pass); echo "<h3>Result</h3>"; if($isSqlInject){ $sql = " SELECT * FROM user WHERE u_user = '". $user."' AND u_pass = '".$pass."'"; try{ echo $sql."<br/>"; $result = mysqli_query($conn,$sql); if(! $result) { die("SQL Error: " . mysqli_error($conn)); } echo " qty : " . mysqli_num_rows($result); if (mysqli_num_rows($result)> 0) { $row = mysqli_fetch_assoc($result); echo " <h3>log in success</h3> <br/>"; print_r($row); mysqli_free_result($result); } else { echo " User or pass incorrect <br/>"; } }catch(Exception $ex){ echo $ex; } mysqli_close($conn); }else{ //http://php.net/manual/en/mysqli.prepare.php $stmt = $mysqli->prepare("SELECT * FROM user WHERE u_user = ? AND u_pass = ?"); $stmt->bind_param("ss", $user, $pass); $stmt->execute(); $result = $stmt->get_result(); $stmt->close(); if ($result->num_rows > 0) { // output data of each row $row = $result->fetch_assoc(); echo " log in success <br/>"; print_r($row); echo "<br/><br/>"; } else { echo " User or pass incorrect <br/>"; } $mysqli->close(); } } ?>
config.php
<?php $conn = mysqli_connect("127.0.0.1","user","pass","dbname" ); // Check connection if (mysqli_connect_errno()) { echo "Failed to connect to MySQL: " . mysqli_connect_error(); }else{ } $mysqli = new mysqli("localhost","user","pass","dbname" ); // Check connection if (mysqli_connect_errno()) { printf("Connect failed: %s\n", mysqli_connect_error()); exit(); } ?>