SQL Injection

  • SQL Injection is a code injection technique that might destroy your database.
  • This time, we will introduce the basic technique of SQL Injection using MySQL.
  • I built an SQL Injection environment on my PC and did everything locally. 我是好人 :).
  • SQL注入简介:
    • SQL注入是通过某种方式向数据库发送非法的SQL语句,并越权获取一些信息。SQL注入常常是因为对用户的输入处理不当,导致黑客可以向数据库发送恶意的SQL语句,利用SQL语法的漏洞执行非法SQL指令获取目标信息,这些信息通常是的管理员账户和密码或者其他用户的隐私信息。
  • 在此篇文章中,我将讲述关于SQL的基本知识,并完成一次人工注入的渗透测试。

基础

Web程序三层架构

  • 界面层 + 业务逻辑层 + 数据访问层
  • 我们先来看看用户获取数据库信息的过程:
    user and server
  1. 用户通过客户端访问网页 request www.baidu.com
  2. 客户端提交表单给前端服务器
  3. 前端服务器向后端服务器发出request请求
  4. 后端服务器给前端服务器一个API,然后向数据库发送SQL语句请求数据
  5. 数据库返回信息,后端服务器进而向前端服务器发送response
  6. 前端服务器处理完数据发送到客户端

SQL 基础语法

  • 在讲语法之前,我们需要先对SQL和MySQL有一些了解:
    1. SQL is case insensitive SQL不区分大小写
    2. MySQL is a relational database MySQL是关系型数据库
    3. SQL is an ANSI standard computer language SQL是一种 ANSI 的标准计算机语言
    4. In MySQL, all SQL statements end by ; . 在MySQL中,所有SQL语句以分号结尾
  • 以下是我们进行SQL注入所需要了解的最基础的SQL语句
    • create and delete database or table
      • create database database_database_name;
      • drop database database_name;
      • create table table_name {some data};
    • select
      • select * from table_name;
      • select col1, col2 from table_name;
    • update
      • update table_name set column1 = value1, column2 = value2 where column3 = value3;
    • insert
      • insert into table_name (col1, col2) values = (value1, value2);
      • insert into table_name values = (values of all columns of this table);
    • delete
      • delete from table_name where col = value;
      • delete from table_name;
      • delete * from table_name;

        Example:

        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
        -- create a database name DBS
        CREATE DATABASE DBS;
        -- enter DBS
        USE DBS;
        -- create a table name Persons
        CREATE TABLE Persons
        (
        id int,
        LastName varchar(255),
        FirstName varchar(255),
        Address varchar(255),
        City varchar(255)
        );
        -- add some data into Persons
        insert into Persons (1, 'So', 'Sam', 'Fifth Avenue', 'New York');
        -- add more data into Persons
        insert into Persons (id, FirstName, LastName, Address, City)
        values =(2, 'Jack', 'So', 'Fifth Avenue', 'New York');
        -- show all stuff in Persons
        select * from Persons;
        -- select some data to show
        select FisrtName, Address from Persons where id = 1;
        -- change some data
        update Persons set Address = 'Chinatown' where id = 2;
        -- delete some data
        delete from Persons where FirstName = `Jack`;
        -- delete all stuff in Person;
        delete from Persons; -- it is the same with "delete * from Persons;"
  • Want to learn more? Check 3wschool for further learning.
  • Now, since we are already familiar with the basic syntax of SQL, it is time for SQL Injection.

Key of SQL Injection

  • Comment syntax is the key of SQL Injection.
    • 注释是SQL注入的精髓。通过注释,我们可以改变动态SQL语句的结构,忽视原本设置的一些限制,执行一些非法操作。
  • SQL有两种注释:
    1
    2
    -- comment using "-- " note that the space is required!
    # comment using #
  • 第一种注释不可以省略结尾的空格
  • The second way (#) may not work for some database. 第二种注释不一定有效
  • 另一个需要注意的是单引号 '
    • SQL语句用只用单引号,单引号包围的是数据,在一个网页的动态SQL语句中,用户输入的数据也是用两个 ' 围着的。一般来说,两个 '中间是一个字符串,而这个能够输入字符串并提交给网页作为动态SQL语句的一部分的输入点就是我们想要的SQL注入点。
    • 比如这条php语句,$user 和 $passwd 是php中的变量,在网站中一般储存了用户输入的数据,它可以通过html提交的表单内容赋值或其他方法获取用户输入的数据,然后在这条语句中作为SQL语句的一部分被提交给服务器。
      1
      $query="select * from user where name='$user' and passwd='$passwd' and visibility = 1;";

      Example:

  • We can use ' to end a string,then inject some SQL statement,and use -- to disable the following statement.
    1
    xxx' or 1=1; -- 
  • 注意,— 后有个空格
  • php处理之后,我们的sql语句是这样的,这也是后端服务器实际发送给数据库的SQL语句:
    1
    select * from user where name= 'xxx' or 1=1; -- and passwd='$passwd' and visibility = 1;";

    通用渗透目标

  • 为了得到目标数据,我们先介绍几个渗透过程中需要注意的重要目标:
    • infromation_schema库:一个信息数据库,保存了MySQL服务器所维护的其他数据库的信息。
    • schemata表:提供了当前MySQL实例中的所有数据库信息,比如show databases的结果。
    • tables表:提供了数据库中表的信息。
    • mysql库:MySQL的核心数据库,主要负责储存数据库用户、权限设置、关键字等mysql管理信息和内核信息。
    • sys库:可以基于IP或用户查询谁使用了最多资源。可以查询哪张表被访问过最多次等。

      人工注入测试

  • 人工注入指的是黑客不借助其他工具,人工地输入注入语句获取信息,通常需要渗透者多次尝试输入,通过抓包(或者在浏览器控制台读取Response)读取服务器反馈,侦察网站漏洞并找出合适的注入语句,进而非法获得信息。
  • 由于本人经验有限,对工具的使用不熟练,便先以人工注入的实例进行讲解.
  • 首先,介绍以下此次实验的环境:
    • Database: MySQL 5.7.26
    • Web servers: Apache 2.4.39
    • Object Website: www.test.com(localhost) written in html and php
  • 目前测试环境网站仅能在我的电脑本地访问(毕竟没买域名也没服务器),暂时以图片的方式来展示,我会把这个测试环境的网站源码放到github上,有需要的自取->SQL-Injection-Environment

Comment Injection

  • 在下面的案例中,我会演示如何利用SQL的注释以及逻辑恒等式来进行SQL注入:
  • 首先,我们进入网站首页。

Step1

  • 进到首页,我们发现有四个选项,还有一些信息。不用急着先进去,可以先Ctrl + U查看网页源码(在后面的每一个网页,我们都需要查看网页源码)
  • 看到源码之后,我们就有些思路了。这个网站很简单。就是一个数据库有一个表,网站可以对其进行增删改查。四个选项也对应了SQL数据库最基础的四个操作
    1. insert
    2. select
    3. update
    4. delete
  • 假设我们要对这个网站进行SQL注入来获得信息,我们最主要的进攻方面应该是这个select窗口。因为select会向网站返回信息,也就是可能有回显

Step2

  • 在进入select之前,别忘了网站的首页有一些文字信息。千万别放过这些有限的文字。在真实的网络环境中,比起慢慢逆向工程去挖掘技术漏洞,有时这些文字更有可能暴露出网站的问题。就是说,要学会运用伟大的社会工程学
    1
    2
    公司需要更换新的通讯系统,需要各位填写一个默认邮箱,请各位及时及时录入好信息
    此次录入的信息将会被用来注册通讯系统账号,此处设置的密码会成为您在新系统的默认密码
  • 由这些文字,我们可以获得这些信息:
    1. 这是一个收录用户邮箱和账户密码的网站
    2. 我们或许可以录入信息并查询自己的信息
  • 这为我们提供了攻击目标和重要突破口

Step3

  • 我们进入select界面,尝试输入前,我们可以先查看源码
  • 我们知道了传输方式post,输入字符上限以及name,passwd关键词。
  • 然后随便输入一些数据看看会返回什么。
    • 在实际环境中,对数据输入的类型会有一定限制,比如邮箱需要用一定格式等等。实际问题中,我们可以做暴力破解之类的方式去尝试,但为了节省时间,这个环境没有特别地去限制。(毕竟这只是随便自建的垃圾靶场)
    • 在实际的测试中,我们也可以直接输入一个 ' 符号提交,看看会不会返回SQL的报错信息,如果返回了syntax error,说明没有对'进行过滤,因为三个 ' 必然会产生语法错误。那么这个地方有99%的可能存在SQL注入漏洞。因为它连 ' 都没有过滤。但是在这个网站行不通,因为我没有让error返回,只返回字符产 “0结果”,这也模拟了现实环境—— 我们的测试不一定能够得到想要的结果。 但实际上这个网站也没有对 ' 进行过滤,只不过它不返回错误,暂时没露出马脚。

  • 我们发现没有返回什么有用的信息,我们可以尝试从控制台下手看看能不能获取有用的信息,按F12
  • 点击NetWork,首先看Headers,这是浏览器捕获的数据包,当然我们也可以用WireShark等抓包工具获得更多信息,但由于这是一个简单的渗透测试,我就不把操作弄得太繁琐了。
  • 我们先仔细看看Header:
    • General中我们可以看到:
    1. 刚才我们在select.html通过POST方法提交数据到当前页面select.php
    2. 其他还包括一些服务器位置端口等可能有用的信息(但由于这是本地测试,所以对此例而言不用特别在意)
    3. 还有URL中出现了关键字name和passwd,这很有可能就是数据库中table的实际column名称,当然网站可以把这些名称改成别的或者隐藏,但没关系,我们后面会有其他方式获取。
    • Response Headers中,我们可以发现Web Server阿帕奇,服务器所在系统Win64
  • 再看看Response的详细内容:
  • 没看出什么特别的。
  • 最后看看Payload,可以看到和这个php脚本有关的一些变量:

Step4

  • 大致探索了select,我们得到了一些可能用的信息。现在回到我们之前的发现:我们可能可以在insert界面输入数据并查询自己的信息,这为我们进一步探索select提供了思路:
  • 先打开录入界面,尝试录入一些数据
  • 发现我们可能成功录入了数据
  • 由于这是渗透测试,我们先让大家看看答案:实际上,数据已经录入我们的数据库中。但在后续的推导中,我们不利用在后台查询出来的信息(为了模拟真实环境),只是先让大家看看。
  • 我们现在回到select界面输入我们刚才录入的信息看看:
  • 可以看到,我们的信息以及被录入了。看返回的数据,有四行,我们可以知道select语句至少指定了四列返回数据。你也可以Ctrl + U 看源码和F12看控制台,但这个网站并不会返回多于我们第一次乱输入的有效信息,我就不在此做重复操作了。

Step5

  • 此前的工作都是对网站进行一个基本探索,到现在,我们已经对这个网站有了基本的了解:
    1. 网站很简陋,从源码可以看出,功能简单直白
    2. 网站防护措施不佳,可以轻易向数据库输入数据
    3. select界面可以返回有价值的信息
  • 接下来,我们就要注入了,分为以下场景:
  1. 我们知道用户名,但不知道密码,想要查询特定用户信息(以Momoyeyu为例:
  • 注入注释符号
    1
    Momoyeyu' -- "
  1. 我们不知道用户名,也不知道密码,只想查询所有信息
    1
    ' or 1=1 -- "

  • 由于1=1是恒等式,我们查询出了所有信息,包括管理员admin,密码nimda123,假设这个网站有管理员登录窗口,且这个管理员账户可用,那我们的SQL注入就已经取得很大成功了。但实际环境中,管理员的信息可能会加一重保险,在我这里也是这样的:
  • 可以看见,在最后一列visibility中,admin的数值是1,而其他都是0,这在我写的php脚本中,这有着明确的过滤:
    1
    $query = "select id, name, email, passwd from user where name='$user' and passwd='$passwd' and visibility = 0;";
  • 这意味着,即使知道管理员账号密码,我们也无法登录。

  • 因为管理员的visibility参数是1
  • 如果像让管理员账号无法通过注释获取,网页可以进行一个很简单的操作:把visibility过滤前置。
    1
    $query = "select id, name, email, passwd from user where visibility = 0 and name='$user' and passwd='$passwd';";
  • 由于这是渗透测试,我没有搭建一套完整的管理员系统。对于管理员有专门过滤的系统,我也还暂时不太了解怎么渗透。但这不妨碍我们继续进行注入获取更多信息。

Union attack

  • 在很多网页中,我们是没法一次性提交多个SQL语句的,比如我们想要注入这样一条语句:
    1
    '; show databases; -- 
  • 如果网页只允许一次提交一个SQL语句时,第二个语句就会完全被忽略,我们没法得到我们想要的数据。这时候,就可以尝试利于union select。
  • 使用union attack有一个前提:网站有回显。也就是说,只有在网站会将select出来的数据返回,才有可能使用union attack。

Step1

1
2
-- 假设我们有一个数据库dbs和t1,里面有name和password
select name, password from dbs.t1 where ... union select 1, 2; --
  • 这段语句会返回符合条件的name,password和1,2,其中1在name列,2在password列。在数据库中来看是这样的:
  • Union其实就是把两句select语句的值整合到一个表单。利用这个语法,我们可以查询更多我们想要的信息
    1
    select name, password from dbs.t1 where ... union select database(), user(); -- 
  • 通过这段注入,我们可以获得现在使用的database和发出指令的user。
  • Union语法需要注意的是,两个select返回的字段数量要相同,也就是说,我们需要猜测出前一个语句的字段数。猜测的方法比较简单:观察网站回显的字段数,这是select字段的最小值,比如网站回显了4种数据,那么select访问字段至少为4,可能会更多,因为它可能会索取更多数据,只不过不回显。这种情况也很简单,从5,6,7这样试下去就行。
  • 还需要注意的是,有时后union前后的语句对应位置的字段如果数据类型不同,可能会报错。比如id是int类型,但我们注入返回的是string,就有可能出现问题,但这不是绝对的。

Step2

  • 接下来,我们把以下语句在select页面进行注入
    1
    ' union select 1,database(),user(),version() -- 
    union1
  • 得到:
    • 数据库名:test
    • 数据库用户:root@localhost
    • 数据库版本:5.7.26
  • 此外,还有如version()等可以注入的函数,就不一一列举了。
  • 我们现在拿到了数据库名,那我们就可以进一步去获得这个数据库的所有表,然后获取表里的所有字段。

Step3

  • 还记得我们在前面提到的重要渗透目标吗?现在就需要用到他们了。首先,针对Information_schema库,用下面这段开始注入:
    1
    ' union select 1, database(), user(), table_name from information_schema.tables where table_schema=database(); -- 
    union2
  • 这句话查询了我们现在所在数据库的所有表名称,由于test数据库只有一张表user,这里只返回了一组数据,如果数据库有更多表,这段注入语句也能将其挖掘出来。但由于这是一个简陋的环境,此数据库只有目标表。
  • 在真实环境中,我们很有可能是从一个回显其他表的地方开始注入,然后进行现在这个步骤才发现数据库有user表,然后以user表为渗透目标进行渗透。此例没有对环境额外做设计,但对实际问题时需要考虑到这里所说的情况。

Step4

  • 下一步就是进一步获取user表中的字段,此例主要是用户名和密码,比如
    • user, username, user_name, name
    • password, passwd
      1
      ' union select 1, database(), user(), column_name from information_schema.columns where table_name='user'; -- 
      union3
      union4
  • 我们发现出来了很多列,我没有全部截屏下来,但并不是所有列都是我们目标表的column,这是我故意留下的一个坑:在mysql库中也有一张user表,我们在这里检索到user的列中包括了mysql.user中的列。因此,我们创建数据表时一般会用users命名避免出现这种问题。
  • 对此例的情况,我们需要区分哪些是我们所需要的字段。一个简单的办法是在自己的数据库中查询mysql中的列字段,然后排除,剩下的就是目标字段。此例中也比较明显,前面我们提到了mysql库主要是和权限有关的库。所以这里很多有_priv后缀的都是mysql库的,可以直接排除。或者对where增加一些限制:
    1
    ' union select 1, database(), user(), column_name from information_schema.columns where table_name='user' and table_schema = database(); -- 
    union5
  • 筛选后的字段我们可以确定为:
    • id
    • name
    • email
    • createtime
    • passwd
    • visibility
  • 获得了所有字段之后,我们就可以用union attack查询这个表中的任何信息。用同样的方法可以获取这个库中其他表的所有信息,由于此例只有一张表,而且方法已经介绍完了,我就不再做过多演示。当然,有时候你获得的密码可能是加密的,这就牵扯到解密问题了,此次我们不对此进行更多讨论。

总结

  • 此次我们了解了基本的SQL语句和一些常见的语法漏洞,并利用这些漏洞完成了一次人工注入。但是,对于现实环境,很多时候人工注入是难以完成的。比如网站对'--进行了过滤,我们基本就用不了此例的注入了。或者对输入字符max值做了限制,后面的union attack由于注入语句过长,也难以成功。实际上,现在SQL注入大多借助了注入工具,比如sqlmap。或许在后面的博客中我们会继续讨论这些话题,敬请期待。