Pipy:保护 Kubernetes 上的应用程序免受 SQL 注入和 XSS 攻击
注入攻击在OWASP Web 应用 10 大安全风险[1]排名 2021 年下滑至第 3 位,多年来一直位居前十。SQL 注入 (SQLi) 是一种用于攻击网站和 Web 应用程序的常见注入技术。没有将用户输入与数据库命令完全分开的应用程序面临着将恶意输入作为 SQL 命令执行的风险。成功的注入攻击可能导致未经授权访问敏感数据,例如密码、信用卡详细信息和个人用户信息。SQL 注入攻击导致最近许多备受瞩目的数据泄露事件,导致企业声誉受损和监管罚款。
常见的解决方法是使用正则表达式列表来过滤流量,这在某些情况下有效,但对于一些复杂的输入或转义输入来说不足。我们并不是要抨击正则表达式,它们很好并且有自己的使用场景,它们只是不适合这种场景。
这篇文章将演示如何使用Pipy[2]一种开源可编程代理来加强安全性,方法是为应用程序添加额外的防护层,来避免受到此类攻击。
在上一篇宣布Pipy 0.70.0最新版本的博文中,介绍了 Pipy 添加了对名为Native Module Interface (NMI)[3]的扩展的支持,我们将使用 Pipy NMI 开发一个模块来集成成熟稳定的开源库libinject[4],用于在请求流量到达应用程序之前针对 SQLi 和 XSS 攻击进行扫描。
作为演示,我们将使用一个开源的经典 LAMP 示例Vulnerable Mama Shop (VMS)[5],该示例是故意具有 SQLi 缺陷。我们首先将通过破解基本应用程序来演示 SQLi 攻击,然后再通过添加 Pipy 作为 Sidecar 来阻止某些 SQLi 攻击来加强应用程序的安全性。
先决条件这篇文章假设有如下访问权限:
•运行 Kubernetes 集群
•kubectl[6]
演示源代码在 GitHub 上,可以从pipy-sqli-demo[7]仓库下载。
部署 Kubernetes 集群和 Vulnerable Mama Shop 应用要在本地运行演示,我们推荐k3d[8]一个轻量级包装器来在 docker 中运行k3s[9](Rancher Lab 的最小 Kubernetes 发行版)。
$k3dclustercreatemy-cluster-p8080:30060@server:0
在上面的命令中,我们创建了一个单节点的集群,并将 k3d 容器的 30060 端口映射到本地 8080 端口,这个端口将在本教程的后面步骤中使用。
部署易受攻击的 Vulnerable Mama Shop 应用我们将部署一个简单的在线商店应用程序,该应用程序附带安装
•Apache 网络服务器
•MariaDB
•在 Apache 上运行并连接到 MariaDB 数据库的 PHP 应用
1.创建一个名为
1-app.yaml
的 YAML 文件,其中包含以下内容:
apiVersion:apps/v1
kind:Deployment
metadata:
name:vms
spec:
selector:
matchLabels:
app:vms
template:
metadata:
labels:
app:vms
spec:
containers:
-name:vms
image:naqvis/mamashop
ports:
-containerPort:80
---
apiVersion:v1
kind:Service
metadata:
name:vms
spec:
ports:
-port:80
targetPort:80
nodePort:30060
selector:
app:vms
type:NodePort
1.部署应用
$kubectlapply-f1-app.yaml
1.确认 pod 已启动并运行,状态为
Running
。可能需要 30-40 秒才能完成启动,因此在继续下一步之前有必要再次运行命令以确认所有 pod 都正常运行。
$kubectlgetpods
NAMEREADYSTATUSRESTARTSAGE
vms-6658dd4478-hn2461/1Running02m12s
1.查询服务
$kubectlgetsvcvms
NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
vmsNodePort10.43.244.253<none>80:30060/TCP5m43s
还记得一开始我们为 K3d 容器做端口映射:8080=>30060 吗?细心的用户可能还会发现,我们为 Vulnerable Mama Shop Web 应用创建了一个 NodePort 服务,节点端口为 30060。
在浏览器中访问 http://localhost:8080 打开应用程序。
破解应用Mama Shop 应用非常简单,只有 3 个页面。主页(如上所示)是用户可以通过下拉框查询待售商品的页面、客户登录页面和关于页面。访问 3 个页面,看看每个页面的作用以及每个页面如何正常工作。
尝试提交一个类别,看看应用是如何展示的。下面显示了饮料类别中的商品列表。
可能已经注意到,选择类别并点击提交不会更改 URL,因此应用正在执行 POST 请求,我们将需要一些工具来拦截或查看网页产生的请求流量。
通常,使用诸如 Blurp 套件或 OWASP ZAP 之类的代理工具来拦截和修改对应用程序的请求。但为了简单起见,我们将使用 Firefox 浏览器开发者控制台来查看和修改请求。
返回浏览器(本演示中的 Firefox)并通过菜单选项或通过 Mac 上的快捷键Option+Cmd+I
或操作系统上的类似功能打开Web 开发人员工具。移动到Tools | Browser Tools | Web Developer Tools
网络选项卡,然后点击网页上的提交按钮以查看网络流量。
我们可以看到所有请求以及详细信息,一个网页向 Web 服务器发送以显示该页面。
网络操作窗口显示,要浏览类别中的项目,一个带有 URL 编码的catid
参数的 HTTP POST 请求被发送到welcome.php
。要测试 SQL 注入,通常会修改用户输入并发送单引号,例如'
。
我们将通过一个简单的实验来确定输入是否正确转义:将 catid 更改为 SQL 查询字符串,看看是否会导致网页上显示的语法错误。
catid=’or1=1;—
执行上述更改并点击网络操作窗口右下角的发送按钮。
哇,我们做到了,welcome.php
显示类别项目的功能存在 SQL 注入漏洞。错误消息还告诉我们数据库是MariaDB。错误告诉还我们or 1 = 1
附近存在语法问题。进一步,我们可以假设数据库查询类似于
SELECTitem_name,item_category,item_descriptionfromitems_tablewhereitem_category=‘or1=1;—;
如果我们将catid
结尾更改为“ or 1 = 1 — ;
,那应该可以解决引号问题。它应该从数据库中选择所有行,这在破解中很有用。
假定的 SQL 查询的结构可能是正确的,尽管它不一定是开发人员使用的精确查询。这对于窃取客户信息等破坏性攻击来说已经足够了。
提取用户数据客户登录页面告诉我们该应用包含客户数据,这可能存储在某种客户或用户表中。
根据目前收集的信息,可以使用MariaDBINFORMATION_SCHEMA
和SQL Union 运算符来检索数据库和表的详细信息。
将参数更改catid
为以下以检索数据库名称,该名称将进一步用于检索表详细信息。
catid=1000unionselectdatabase(),“A”,“B”fromdual
下面显示了来自 Vulnerable Mama Shop 的包含数据库名称的响应appdb
。
现在我们有了数据库名称,我们可以获取数据库中的表
catid=1000unionselecttable_name,version,table_commentfrominformation_schema.TABLESwheretable_schema=‘appdb’
这样就得到了所有表名,我们可以假设数据库中的 users 表包含用户名和密码。
我们需要检索users
表的列,并且需要确保使用与从产品表中查询的相同数量的列UNION
才能工作。
catid=1000unionselecttable_name,COLUMN_NAME,DATA_TYPEfrominformation_schema.COLUMNSwheretable_name=‘users’
现在我们知道users
表中总共有六列,其中包含可以从users
表中获得的firstname
、lastname
、nric
、password
、email等详细信息
。
我们已经收集了足够用来转储用户列表的信息。以下输入可用于转储用户列表及其firstname
、password
和email
地址。
catid=1000unionselectfirstname,nric,emailfromusersLIMIT7,100
LIMIT
和offset
的值可以通过观察正常请求中有多少条目来计算。offset
值可以删除这些条目。可以设置一个足够大的LIMIT
值,以便可以转储客户记录。
可以尝试是否可以使用提取到的凭证信息来登录。
使用 Pipy Sidecar 阻止某些 SQLi 攻击当未经验证的用户输入与 SQL 指令混合时会发生 SQL 注入,因此开发人员应更加注意转义用户输入(例如使用参数化查询),但作为 Kubernetes 工程师也可以通过防止这种情况来帮助避免 SQL 注入攻击到达应用。这样,即使应用易受攻击,仍然可以阻止攻击。
有多种方法可以保护应用,但对于这篇文章,我们将重点关注注入一个 sidecar 容器来代理所有流量并拒绝任何被检测为SQLi攻击的请求。
部署 Pipy Sidecar作为演示,我们使用手动注入 sidecar 的方法,但实际上,手动将代理部署为 sidecar 并不是最好的解决方案。
1.使用以下内容创建一个名为
2-app-sidecar.yaml
的 YAML 文件,并检查如下重要部分:
•运行 Pipy 的 sidecar 容器监听 8000 端口
•Pipy 进程将所有流量转发到应用
•包含 SQLi 的请求 URI 或 POST 正本会被拒绝
•应用的服务首先将所有流量路由到 Pipy sidecar 容器。
apiVersion:apps/v1
kind:Deployment
metadata:
name:vms
spec:
selector:
matchLabels:
app:vms
template:
metadata:
labels:
app:vms
spec:
containers:
-name:vms
image:naqvis/mamashop
ports:
-containerPort:80
-name:pipy#<--sidecar
image:naqvis/pipy-nmi
env:
-name:PIPY_CONFIG_FILE
value:/etc/pipy/nmi/nmi.js
ports:
-containerPort:8000
volumeMounts:
-mountPath:/etc/pipy/nmi
name:pipy-pjs
volumes:
-name:pipy-pjs
configMap:
name:sidecar
---
apiVersion:v1
kind:Service
metadata:
name:vms
spec:
ports:
-port:80
targetPort:8000#<--thetrafficisroutedtotheproxy
nodePort:30060
selector:
app:vms
type:NodePort
---
apiVersion:v1
kind:ConfigMap
metadata:
name:sidecar
data:
nmi.js:|-
pipy({
_rejected:undefined,
})
.import({
__is_sqli:'lib-inject',
__is_xss:'lib-inject',
__sqli_fingerprint:'lib-inject',
})
.listen(8000)
.demuxHTTP().to(
$=>$
.use('/etc/pipy/modules/inject-nmi.so')
.handleMessage(()=>_rejected=(__is_sqli||__is_xss))
.branch(
()=>_rejected===true,(
$=>$
.handleMessageStart(_=>
console.log(`SQLInjectionfoundwithFingerprint:${__sqli_fingerprint}`))
.replaceMessage(newMessage({status:403},'Forbidden'))
),
()=>_rejected===false,(
$=>$.muxHTTP().to(
$=>$.connect('localhost:80')
)
)
)
)
1.部署应用
$kubectlapply-f2-app-sidecar.yaml
等待 pod 启动并准备就绪
$kubectlgetpods
NAMEREADYSTATUSRESTARTSAGE
vms-945f6f85c-8v7sb2/2Running08m48s
测试 sidecar
返回应用页面并再次尝试 SQL 注入来测试 Sidecar 是否正常过滤流量。Pipy sidecar 在请求到达应用程序之前阻止它!
Sidecar 通过返回403 Forbidden来阻止流量。使用我们之前尝试过的的 SQLi 来执行更多请求,我们都将得到 403 作为响应。我们可以通过查看 Pipy sidecar 的日志来验证这一点。