SSRF漏洞
SSRF全称为Server-side Request Fogery,即服务器端请求伪造,是一个由攻击者构造请求,在目标服务端执行的一个安全漏洞,简单来说就是利用服务器漏洞以服务器的身份发送一条构造好的请求给服务器所在内网进行攻击
这里引用一张图进行理解
当攻击者想要访问服务器B上的服务,但是由于存在防火墙或者服务器B是属于内网主机等原因导致攻击者无法直接访问,如果服务器A存在SSRF漏洞,这时攻击者可以借助服务器A来发起SSRF攻击,通过服务器A向主机B发起请求,达到攻击内网的目的
形成成因
由于服务端提供了从其他服务器获取数据的功能,但没有对目标地址做过滤与限制,利用改漏洞获取内部系统的一些信息(因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内网系统),通过对服务器发送请求形成漏洞
产生漏洞函数
1 | file_get_contents() |
file_get_contents()
这个函数的作用是将整个文件读入一个字符串中,并且此函数是用于把文件的内容读入到一个字符串中的首选方法。
fsockopen()
使用fsockopen函数实现获取用户制定url的数据(文件或者html)
curl_exec()
该函数可以执行给定的curl会话
内网IP地址
IPv4地址协议中预留三个地址段作为私有地址,三个地址段分别位于A、B、C三类地址内:
A类地址:10.0.0.0–10.255.255.255
B类地址:172.16.0.0–172.31.255.255
C类地址:192.168.0.0–192.168.255.255
IP地址范围:1.0.0.1——255.255.255.254
IP地址(1.0.0.1——255.255.255.254)分类:
A类:1.0.0.1—127.255.255.255
B类:128.0.0.1—191.255.255.254
C类:192.0.0.1—223.255.255.254
D类:224.0.0.1—239.255.255.254
E类:240.0.0.1—255.255.255.254
0.0.0.0为当前主机
绕过方法
部分存在可能产生SSRF漏洞做了白名单或者黑名单的处理,来达到阻止对内网服务和资源的攻击和访问
限制为http://www.xxx.com 域名时(利用@)
同时可以采用进制转换绕过127.0.0.1
八进制:0177.0.0.1;十六进制:0x7f.0.0.1;十进制:2130706433
添加端口号 127.0.0.1:8080
利用句号 127。0。0。1 会解析为 127.0.0.1
ip地址转换原理
IP地址一般是一个32位的二进制数意思就是如果将IP地址转换成二进制表示应该有32为那么长,但是它通常被分割为4个“8位二进制数”(也就是4个字节每,每个代表的就是小于2的8 次方)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)
常见利用协议
Http协议: 最常用的SSRF漏洞利用协议,作用为直接访问http资源
File协议:可利用此协议进行服务器文件读取
Dict协议:可用此协议进行端口开放探测
Gopher协议: gopher支持发出GET、POST请求,可进行复杂的漏洞利用
Gopher协议
可以实现多个数据包整合发送,然后gopher 服务器将多个数据包捆绑着发送到客户端,使用tcp 进行可靠连接
gopher url 格式为:
1 | gopher://<host>:<port>/<gopher-path> |
<port>
默认为70
如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
[De1CTF 2019]SSRF Me
网页源码,这里需要将其格式化
1 | #! /usr/bin/env python |
长代码进行分段解析
1 | class Task: |
进行简单的赋值,并进行文件夹创建
1 | def Exec(self): |
进行判断后对文件继续写入和读取
1 | @app.route("/geneSign", methods=['GET', 'POST']) |
建立 /geneSign
路由
1 | @app.route('/De1ta',methods=['GET','POST']) |
建立/De1ta
路由,读取get的传参中的param的值和cookie中的action和sign,并进行绕过判断
1 | def getSign(action, param): |
定义getSign方法,连接secert_key,param和action,并进行md5加密
1 | def waf(param): |
简单过滤判断
由提示可以得到fag is in ./flag.txt
通过scan方法读取到flag.txt中的内容,再通过Exec方法将flag.txt中的内容写入result.txt,从而通过读取result.txt读取flag
这里需要通过getSign(self.action, self.param) == self.sign
,这里就可以用/geneSign
路由进行调试从而得到self.sign的值,
getSign方式进行的连接方式为key+param+action,action = "scan"
将action的值在/geneSign
路由中固定为scan,而在Exec方法中需要self.action的值内存在scan和read,就可以构造/geneSign
中的param的值为flag.txtread,/De1ta
中的param的值为flag.txt
[网鼎杯 2020 玄武组]SSRFMe
1 | <?php |
源码提示需要在本地端访问hint.php,但在调用时对其进行了限制
check_inner_ip方法判断不为空后,检测是否为内网ip地址,safe_request_url方法调用check_inner_ip方法进行判断,不符合即执行给定的curl会话,这里即需要绕过内网ip地址对hint.php进行本地访问
这里可以直接通过http://0.0.0.0进行绕过(0.0.0.0的IP地址表示整个网络,代表所有主机的ipv4地址)
check_inner_ip中的url_parse中存在的漏洞
1 | <?php |
打开hint.php文件后,可以看到提示redispass is root
,这里就需要用到redis的主从复制,所以先简单进行一个解释
redis的主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
redis的持久化使得机器即使重启数据也不会丢失,因为redis服务器重启后会把硬盘上的文件重新恢复到内存中,但是如果硬盘的数据被删除的话数据就无法恢复了,如果通过主从复制就能解决这个问题,主redis的数据和从redis上的数据保持实时同步,当主redis写入数据是就会通过主从复制复制到其它从redis。
然后因为redis采用的resp协议的验证非常简洁,所以可以采用python模拟一个redis服务的交互,并且将备份的rdb数据库备份文件内容替换为恶意的so文件,然后就会自动在节点redis中生成exp.so(exp.so 文件是一个Redis 模块,它提供了一些命令和功能,可以让攻击者在Redis 服务器中执行任意代码,从而获得服务器的控制权),再用module load命令(自动加载命令)加载so文件即可完成rce,这就是前段时间非常火的基于主从复制的redisrce的原理
简单来说,利用redis的主从复制性质,通过植入恶意的redis服务器作为主节点,传输exp到从节点中(即目标redis服务器),通过数据同步响应执行命令即可。
这里需要用到两个工具
https://github.com/n0b0dyCN/redis-rogue-server(提供需要的exp.so)
https://github.com/xmsec/redis-ssrf(模拟redis服务)
运行redis-ssrf工具中的rogue-server.py模拟成主redis,将恶意的exp.so文件放在同一目录下,将该文件传输至从redis,从而实现对从redis的远程控制
lhost为主redis服务器,command为执行操作
成功传输后就可以对从redis进行命令执行,这个就通过ssrf-redis.py生成执行命令,对从redis进行控制(这里由于get传参,所以在进行一次url加密)
(感觉除了网页显示,也可以通过检测主redis端口也可以)
[GKCTF 2021]hackme
进入网页为登录页面,可以看到提示是使用nosql
这里就先了解一下nosql
NoSQl
NoSQL 即 Not Only SQL,意即 “不仅仅是SQL”,支持在关系数据库中发现的传统结构之外存储和查询数据
NoSQL注入主要有两种注入方式:
第一种是按照语言的分类,可以分为:PHP 数组注入,JavaScript 注入和 Mongo Shell 拼接注入等等
第二种是按照攻击机制分类,可以分为:重言式注入,联合查询注入,JavaScript 注入、盲注等,这种分类方式很像传统 SQL 注入的分类方式。
重言式注入
又称为永真式,此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。
联合查询注入
联合查询是一种众所周知的 SQL 注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。
JavaScript 注入
MongoDB Server 支持 JavaScript,这使得在数据引擎进行复杂事务和查询成为可能,但是传递不干净的用户输入到这些查询中可以注入任意的 JavaScript 代码,导致非法的数据获取或篡改。
盲注
当页面没有回显时,那么我们可以通过
$regex
正则表达式来达到和传统 SQL 注入中substr()
函数相同的功能,而且 NoSQL 用到的基本上都是布尔盲注。
这里以MongoDB进行简单解释这几种注入方式(暂时先不用JavaScript)
数据库(Database)
一个 MongoDB 中可以建立多个数据库
集合(Collection)
集合就是 MongoDB 文档组
文档(Document)
文档是一组键值(key-value)对
需要介绍一下一些简答的语法
使用 find()
方法来查询文档
1 | db.collection.find(query, projection) |
query:可选,使用查询操作符指定查询条件,相当于 sql select 语句中的 where 子句
find()
方法可以传入多个键值对,每个键值对以逗号隔开
1 | {key1: value1, key2:value2} |
OR 条件语句使用了关键字 $or
来表示
1 | {$or: [{key1: value1}, {key2:value2}]} |
等于 | { |
---|---|
小于 | { |
小于或等于 | { |
大于 | {<key>:{$gt:<value>}} |
大于或等于 | {<key>:{$gte:<value>}} |
不等于 | {<key>:{$ne:<value>}} |
重言式注入
1 | array( |
查询命令
1 | db.users.find({'username':$username, 'password':$password}) |
可以通过 $ne
关键字构造一个永真的条件就可以完成 NoSQL 注入
1 | {"username":{"$ne":1},"password": {"$ne":1}} |
执行查询语句
1 | db.users.find({'username':{"$ne":1},'password':{"$ne":1}}) |
即在users中寻找username 和 password 都不等于 1的文档
联合查询注入
联合查询与sql注入相似
1 | username=admin', $or: [ {}, {'a': 'a&password=' }], $comment: '123456 |
执行查询命令
1 | { username: 'admin', $or: [ {}, {'a':'a', password: '' }], $comment: '123456'} |
盲注
盲注基本与sql注入一致,通过正则匹配进行查询
这道题先尝试用重言式注入进行登录
发现存在过滤,看到提示
这里进行unicode加密
这里应该密码错误,所以进行盲注,爆出密码
1 | import requests |
可以得到密码42276606202db06ad1f29ab6b4a1307f
登录后发现是一个文件测试,尝试了一下可以进行文件读取
查看/flag,发现flag在内网
就需要结合提示
因为要用到server和配置文件,就先写一下一些基础知识
/etc/passwd
该文件储存了该Linux系统中所有用户的一些基本信息,只有root权限才可以修改。其具体格式为 用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell(以冒号作为分隔符)
/proc/self/cmdline
该文件包含的内容为当前进程执行的命令行参数
/proc/self/mem
/proc/self/mem是当前进程的内存内容,通过修改该文件相当于直接修改当前进程的内存数据
/proc/self/environ
/proc/self/environ文件包含了当前进程的环境变量
/proc/self/fd
这是一个目录,该目录下的文件包含着当前进程打开的文件的内容和路径
/proc/self/exe
获取当前进程的可执行文件的路径
查看进程环境变量,可以看到使用的式nginx
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf 或 /etc/nginx/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
访问/usr/local/nginx/conf/nginx.conf,可以看到有个weblogic服务
这里就需要通过http请求走私来进行绕过(Ngnix < 1.17.7 中error_page 存在请求走私的漏洞,此处Ngnix为1.17.6,这就可以走私到WebLogic Console登录页面)(这里好像都是利用本身存在的漏洞,做的时候一直都没成功走私,这里就简单记录一下知识点)
HTTP 请求走私
HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列的方式的技术。使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。
这里引用一个图来进行简单解释,请求走私发生多是由于前端服务器和后端服务器对传入的数据理解不一致造成,其原因是由于http提供的两种不同请求结束方式,即 Content-Length
和 Transfer-Encoding
标头
这也就造成其三种分类
CLTE:前端服务器使用 Content-Length
头,后端服务器使用 Transfer-Encoding
头
TECL:前端服务器使用 Transfer-Encoding
标头,后端服务器使用 Content-Length
标头。
TETE:前端和后端服务器都支持 Transfer-Encoding
标头,但是可以通过以某种方式来诱导其中一个服务器不处理它
这也提供了五种攻击方式
CL不为0
所有不携带请求体的HTTP请求都有可能受此影响
前端代理服务器允许GET请求携带请求体;后端服务器不允许GET请求携带请求体,它会直接忽略掉GET请求中的Content-Length
头,不进行处理。这就有可能导致请求走私
1 | GET / HTTP/1.1 |
前端服务器收到该请求,读取Content-Length
,判断这是一个完整的请求。
然后转发给后端服务器,后端服务器收到后,因为它不对Content-Length
进行处理,由于Pipeline
的存在,后端服务器就认为这是收到了两个请求
第一个:
1 | GET / HTTP/1.1 |
第二个:
1 | GET / secret HTTP/1.1 |
从而造成请求走私
CL-CL
在RFC7230的第3.3.3节中的第四条中,规定当服务器收到的请求中包含两个
Content-Length
,而且两者的值不同时,需要返回400错误。
中间代理服务器按照第一个Content-Length
的值对请求进行处理,而后端源站服务器按照第二个Content-Length
的值进行处理,当后端值小于代理器的值时,仍会残留部分在缓冲区,而当再次对服务器进行请求,就会对后一次请求造成影响
CL-TE
CL-TE,就是当收到存在两个请求头的请求包时,前端代理服务器只处理Content-Length
请求头,而后端服务器会遵守RFC2616
的规定,忽略掉Content-Length
,处理Transfer-Encoding
请求头
1 | POST / HTTP/1.1\r\n |
由于前端服务器处理Content-Length
,所以这个请求对于它来说是一个完整的请求,请求体的长度为6,也就是
1 | 0\r\n |
而后端服务器处理Transfer-Encoding
,当它读取到
1 | 0\r\n |
就以为结束了,导致a残留在缓冲区,与CL-CL情况一致,对后一次请求造成影响
TE-CL
TE-CL,就是当收到存在两个请求头的请求包时,前端代理服务器处理Transfer-Encoding
请求头,后端服务器处理Content-Length
请求头。
1 | POST / HTTP/1.1\r\n |
前端服务器处理Transfer-Encoding
,当其读取到
1 | 0\r\n |
认为是读取完毕了
后端服务器处理Content-Length
请求头,因为请求体的长度为4
.也就是当它读取完
1 | 12\r\n |
就读取结束了
从而造成后面形成另一个请求
1 | aPOST / HTTP/1.1\r\n |
从而造成请求走私
TE-TE
TE-TE,当收到存在两个请求头的请求包时,前后端服务器都处理Transfer-Encoding
请求头,对发送的请求包中的Transfer-Encoding
进行某种混淆操作(如某个字符改变大小写),从而使其中一个服务器不处理Transfer-Encoding
请求头,在某种意义上这还是CL-TE
或者TE-CL