Showing posts with label SQL Injection. Show all posts
Showing posts with label SQL Injection. Show all posts

Saturday, September 11, 2010

LEET MORE CTF 2010 write up - Oh Those Admins!

This challenge is SQL injection. The line of problem is (I cannot remember the exact column and table name)
$r = mysql_query("SELECT login FROM admin WHERE password='" . md5($_GET['password'], true) . "'");
Normally, web sites keep the password hash as hex string. But this challenge keep raw hash (binary 16 bytes) in database. Binary can be string. So To bypass login, the output of md5 has to look like
' or 1=1#
The above string is what I thought first. But it is too long. To make the brute forcing fast, the required output string should be short. After checking from MySQL doc, I could make it shorter. Here is the list what I found
'or 1#    <== no need for space after single quote, any non-zero number is TRUE
'||1#     <== || is same as OR, no need for space after ||
Before writing the code for brute forcing, I remembered I can put non-printable characters in password using '%XX'. So 1 byte can be 256 values. No charset.

My code for brute forcing is http://pastebin.com/2xMG9rKi.

Ran it about 20 minutes on my slow windows pc, then got it.

password: 34b854c8
password: %34%b8%54%c8
result: c13e807082277c7c36231ed0dd34a863
result: ม>€p‚'||6# ะ4จc

Note: on linux, compile with "gcc -O2 -lssl -o prog prog.c" Note: If you want to see result from my program quick, run it with "./prog 4 4 200"

Submit it from url bar
url?password=%34%b8%54%c8
,then got the real admin hash. It is "071CC0720D0ABD73F61A291224F248D6". But I could not reverse admin hash :( so poor me. When searched in google, I found it in hashkiller but it was not solved.

Below is not my solution

Before finished this post, I found other 2 writeups of this challenge. Very nice solutions. They found shorter SQL injection string.

First is http://cvk.posterous.com/sql-injection-with-raw-md5-hashes. The SQL injection string is
'||'
Here is the modification of my code: http://pastebin.com/ThxBESPs. Got the result in a few minutes.
password: 2c55c819
password: %2c%55%c8%19
result: 3157e727097c7c27342e7dc2729f75ed
result: 1W็'    ||'4.}ยrŸuํ
Second is http://blog.nibbles.fr/?p=2039. The SQL injection string is
'='
Here is the modification of my code: http://pastebin.com/w5E54PNz. Got the result in a second.
password: 22a80f
password: %22%a8%0f
result: 047f1f9ed77f467a273d279d8e521422
result:   žืFz'='ŽR "

Sunday, August 1, 2010

PHP magic_quotes_gpc and SQL injection

เรื่อง magic_quotes_gpc ของ PHP อาจจะดูเก่าไปหน่อย เพราะ feature นี้ใน PHP 5.3 ก็ deprecated แล้ว และก็จะไม่มีใน PHP 6 แต่ผมก็ยังเชื่อว่าหลายๆ คนยังไม่เข้าใจว่า magic_quotes_gpc มันช่วยและไม่ช่วยป้องกัน SQL injection ยังไง

ตาม document ของ PHP ถ้า magic_quotes_gpc ได้ถูกเปิดใช้งาน ตัวอักษร ' (single-quote), " (double-quote), \ (backslash) และ NULL ในข้อมูล GET, POST, COOKIE จะถูก escape ด้วย backslash อัตโนมัติ

กรณีที่ 1 SQL injection กับ MySQL
ผมขอเริ่มด้วยกรณีที่ magic_quotes_gpc ช่วยป้องกัน เรื่อง SQL injection ตัวอย่างนี้  PHP programmer ทุกๆ คนน่าจะเคยเห็นแล้ว
นั่นคือ code สำหรับการทำ authentication โดยข้อมูลอยู่ใน MySQL database (สมมติว่าเก็บ password เป็น plaintext)
// ... initialization and establishing database connection ...
$result = mysql_query("SELECT * FROM account WHERE username='".$_GET['username']."' AND password='".$_GET['password']."'");
if (mysql_num_rows($result) > 0) {
  // authenticated
}
else {
  // login failed
}
ทดสอบด้วย classic SQL injection คือ username เป็น
admin 
และ password เป็น
' or 1=1# 
ถ้าปิด magic_quotes_gpc จะได้ SQL command เป็น
SELECT * FROM account WHERE username='admin' AND password='' or 1=1#'
หลังเครื่องหมาย # ใน MySQL คือ comment จะไม่รวมใน SQL command และโดยทั่วไป (รวมถึง MySQL) operator precedence ของ AND จะสูงกว่า OR นั่นคือ AND จะถูกคิดก่อน OR ถ้าใส่วงเล็บให้ดูง่ายก็จะเป็น
SELECT * FROM account WHERE (username='admin' AND password='') or 1=1
เนื่องจาก 1=1 เป็นจริงเสมอ ก็ทำให้ SQL command ของข้างบนเท่ากับ
SELECT * FROM account
ซึ่งก็จะ login ผ่าน :)

ในทางกลับกัน ถ้าเปิด magic_quotes_gpc จะได้ SQL command เป็น
SELECT * FROM account WHERE username='admin' AND password='\' or 1=1'
ก็จะ login ไม่ผ่าน :(

จะเห็นว่า ถ้าจะทำ SQL injection ในกรณีนี้ต้องมีการใช้ single-quote เข้าไปใน username หรือ password แต่ magic_quotes_gpc จะ escape single-quote ทำให้ไม่สามารถทำ SQL injection หรือพูดได้ว่า
magic_quotes_gpc ป้องกัน SQL injection ได้ ถ้า SQL injection ต้องใช้ตัวอักษร ' (single-quote), " (double-quote), \ (backslash) หรือ NULL

กรณีที่ 2 SQL injection กับ database อื่นๆ
ผมขอใช้ code เดิมจากกรณีที่ 1 แต่เปลี่ยน database เป็นตัวอื่นที่ escape single-quote ตาม SQL standard (เช่น Oracle, MSSQL) คือใช้ single-quote สองตัว เช่น 'O''hh' แต่ครั้งนี้ password ต้องเปลี่ยนเป็น
' or 1=1-- 
(-- คือ comment ตาม SQL standard)
คราวนี้ขอแสดงเฉพาะกรณีเปิด magic_quotes_gpc ก็จะได้ SQL command เป็น
SELECT * FROM account WHERE username='admin' AND password='\' or 1=1--'
สำหรับ database ที่ escape single-quote ตาม SQL standard จะมองว่า password คือ \ (backslash) ซึ่งก็จะ login ผ่านอยู่ดี :)

หลายคนอาจจะบอกว่า PHP กับ MSSQL ไม่น่าจะมีคนใช้ ส่วน Oracle ก็คงมีใช้น้อยมาก อันนี้ผมเห็นด้วย แล้ว PostgreSQL ละ
PostgreSQL ตอนแรกก็ใช้ backslash เพื่อที่จะ escape single-quote แต่ใน version 8 ก็จะมี option standard_conforming_strings ให้เลือกใช้แบบเดิม (สำหรับ backward compatibility) หรือแบบ SQL standard โดยที่ทาง PostgreSQL เองก็แนะนำให้ใช้ตาม standard

Note: PHP มี option magic_quotes_sybase เพื่อที่จะ escape single-quote สำหรับ database ที่ทำตาม standard

กรณีที่ 3 SQL injection กับข้อมูลที่เป็นตัวเลข
ตัวอย่างที่พบบ่อยๆ ของกรณีนี้ คือการแสดงข้อมูลตาม id ที่อยู่ใน database โดยที่ PHP code ที่ทำ SQL query เป็นดังนี้
mysql_query("SELECT col1, col2, col3 FROM data WHERE id=".$_GET['id']);
ทดสอบด้วยการใส่ค่า id เป็น
0 UNION SELECT username,password,1 FROM account 
จะได้ SQL command เป็น
SELECT col1, col2, col3 FROM data WHERE id=0 UNION SELECT username,password,1 FROM account
สมมติว่าใน database ไม่มี id ที่เป็น 0 SQL command ก็จะเป็น
SELECT username,password,1 FROM account
ทำให้แทนที่จะดึงข้อมูลจาก data table ก็กลายเป็นดึงข้อมูลมาจาก account table มาแสดงผล

จะเห็นว่า id เป็นตัวเลข และโดยส่วนมาก programmer ไม่ได้ใส่ single-quote สำหรับตัวเลข ดังนั้น SQL injection ไม่ต้องมี single-quote ก่อนที่จะเพิ่ม SQL command และ magic_quotes_gpc ก็ไม่สามารถช่วยป้องกันในกรณีได้ เพราะไม่มีตัวอักษรที่ต้อง escape

ตัวอย่างอื่นๆ ของ SQL injection กับข้อมูลที่เป็นตัวเลข คือเวลาแสดงข้อมูลเป็นหลายๆ หน้า ตัวเว็บจะมีการรับหน้า และจำนวนข้อมูลต่อหน้า

กรณีที่ 4 SQL injection กับ SQL operator
กรณีนี้ส่วนมากจะพบในหน้า Advanced Search ของเว็บที่ให้ผู้ใช้สามารถใส่หลายๆ เงื่อนไขในการค้นหา และสามารถเลือกได้ว่าจะนำมา AND หรือ OR โดยที่ทางเว็บส่ง AND หรือ OR ตรงๆ เช่น
HTML code snippet:
col1: <input name="col1" type="text" /><br/>
<input checked="checked" name="oper" type="radio" value="AND" /> และ
<input name="oper" type="radio" value="OR" /> หรือ<br/>
col2: <input name="col2" type="text" />
PHP code snippet:
$result = mysql_query("SELECT col1, col2, col3 FROM data WHERE col1='%".$_POST['col1']."%' ".$_POST['oper']." col2='%".$_POST['col2']."%'");
จากตัวอย่างผู้ใช้สามารถเงื่อนไขของ col1 กับ col2 และมี radio box เพื่อเลือก AND หรือ OR โดยตัวอย่าง SQL injection ก็คือแก้ไขค่า oper เป็น
UNION SELECT username,password,1 FROM account#
ทำให้ SQL command เป็น
SELECT col1, col2, col3 FROM data WHERE col1='%cond1%' UNION SELECT username,password,1 FROM account# col2='%cond2%'
ซึ่งก็คือค้นหาข้อมูลใน data table ตาม cond1 และข้อมูลใน account table ทั้งหมด

Note: ถ้า SQL command ใน PHP code ซับซ้อนกว่านี้ การทำ SQL injection ก็ยังเป็นไปได้ แค่อาจต้องเพิ่มบางส่วน เช่น วงเล็บ เพื่อให้ SQL syntax พร้อมกับทำ injection

จะเห็นว่ากรณีนี้ ก็เป็นอีกกรณีหนึ่งที่ magic_quotes_gpc ไม่สามารถป้องกัน SQL injection ได้

จากตัวอย่างทั้งหมดที่กล่าวมา คงทำให้เห็นกันแล้วว่า magic_quotes_gpc ช่วยและไม่ช่วยป้องกัน SQL injection ยังไง (จริงๆ คือนึกไม่ออกแล้ว)
ถ้ามีการเปิด magic_quotes_gpc ก็ยังสามารถทำ SQL injection ถ้าไม่จำเป็นต้องใช้ตัวอักษรที่ถูก escape
และก็จะจบก็คงต้องเขียนวิธีป้องกัน ที่มีอยู่หลักเดียวง่ายๆ คือตรวจสอบ หรือ filter input ที่มาจากผู้ใช้ทั้งหมด ก่อนนำมาประมวลผล

วิธีการป้องกันตามตัวอย่างที่ได้ยกมา (สมมติว่าไม่มีการใช้ magic_quotes_gpc)
1. สำหรับข้อมูลที่เป็นตัวอักษร ใช้
mysql_real_escape_string()
ก่อนที่ใช้นำไปใส่ใน SQL command
2. สำหรับข้อมูลตัวเลข ให้ทำการ cast เป็นตัวเลขก่อนด้วยการใช้
(int)
หรือ
(integer)
หรือใช้
intval()
function
3. สำหรับข้อมูลที่เป็นส่วนหนึ่งของ SQL syntax ก็ให้ตรวจสอบว่าเป็นที่เราอนุญาตหรือไม่ เช่นตัวอย่างในกรณีที่ 4 ก็ตรวจสอบว่าค่า oper เป็น AND หรือ OR เท่านั้น

สุดท้ายการที่ทาง PHP จะเอา magic_quotes_* ออกนั้น น่าจะเป็นเพราะว่า (อันนี้ความคิดผม) มันทำให้ programmer เข้าใจผิด และอาจทำให้สับสน รวมทั้งข้อมูลที่รับมาอาจจะไม่ได้เอามาใช้กับ database เช่น save ลงไฟล์ ทำให้ programmer ต้องมาแก้ไขข้อมูลให้ถูกต้องอีกด้วย
stripslashes()