服务端URL处理问题学习笔记 SSRF/CRLF/其他

LCTF里好多涉及到服务端对url进行处理的问题啊,也暴露出了自己对这一块知识的严重欠缺,ssrf也做过些笔记,但总感觉还不足以专门写东西发出来,通过在LCTF中的一些知识点收集以及自己的一些搜索,感觉在服务端的URL处理这块已经比较全面了,故做下整理,虽然现在认识还很浅,以后慢慢补充吧,保持更新~

顺便膜一下orange大大,orange师傅在blackhat上的议题再一次刷新了自己对一名真正的黑客的认知,这里就不奢望能达到orange师傅的水平了,只是希望自己能在以后的安全路上时时警醒自己,一名真正的安全从业人员应该是什么样的。

还是先从场景开始,我们经常可以看到用户提交一条URL,之后服务器会对此链接进行处理,如果进行访问并对用户做出某些反馈的话,在操作不当的情况下就可能产生各种各样的问题,基本就是以下几个方面

  • ssrf 对任意url都进行访问,就有可能实现内网探测,文件读取等功能
  • crlf 实现HTTP头的注入,因为可以控制HTTP头了,就可用于构造各种基于HTTP的协议。可以控制cookie所以也可能实现会话控制
  • 利用一些发送请求库中的特殊功能,这就需要仔细阅读发送请求库的功能了

下面就对上面这三点展开详细的介绍

SSRF

我们如果想限制用户发来的危险url的话,就需要对host进行一些判断,这里ph师傅有一篇文章写的很全谈一谈如何在Python开发中拒绝SSRF漏洞

ph师傅讲一个比较安全的判断流程应该是这样的

  1. 解析目标URL,获取其Host

  2. 将域名进行解析成ip

  3. 将ip统一转化成相同的格式

  4. 然后判定ip是不是属于局域网网段

  5. 对每一次访问都要做出上述判定,因为考虑到302跳转的存在

那么如果并没有按照上述推荐流程来处理用户发来的url的话,我们可以看到如下攻击方式:

在URL解析上混淆

orange大大在bh上的议题中已经做了详细的阐释并归纳了一张图告诉我们哪些库的处理可能存在问题

url虽然在RFC2396, RFC3986 进行了明确的标准定义,但这只是推荐标准,具体不同的语言,甚至不同的库都有其自己的实现,所以可能url解析获取并验证的是一种host,而在发送请求的库中使用的是另一种host,就造成了这种问题,要避免这种问题,url的解析与url的请求需要使用相同的标准。orange也举了一些php中解析不一致的例子,这些环境下会存在漏洞:

php的parse_url() 与readfile()的解析不一致

php中parse_url() 与curl解析不一致

我们可以看到,不仅是php中的parse_url(),其他的注入NOdeJS的URL(),Go的net/url等,在url的解析上都与curl出现了差异。

第一种情况在curl之前的版本中已经修复了

第二种情况在curl7.54.0版本中也会修复

到这里我们可以简单考虑下,为什么通过上述的符号会存在解析不一致呢?在url中,@符号之前表示user成分,@之前的的user中又通过:对user与pass做了分割,#后面是fragment成分,port前需要有:,host则是以/或者:或者?等作为结尾等等,可以看到这么多的关键字符在里面,那么他们同时出现时应该以什么优先级进行解析?多个相同符号出现时应该以前面的还是后面的为准?正是这些并没有统一规定的东西最终造成了解析对url解析不一致的问题。

这种问题是如此严重,我们可以看到同一个url,甚至python的三种库中都产生了不同的解析结果 XD

没有对域名进行ip转换的话,可以通过一些奇特的域名将ip解析到内网里面

通过这个网站http://xip.io/可以将域名解析到本地ip

1
2
3
4
ping 127.0.0.1.xip.io -n 1
正在 Ping 127.0.0.1.xip.io [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128

通过其他支持的ip写法来绕过host检测

  • 八进制及八进制缩写 0177.0.0.1 017700000001
  • 十六进制及十六进制缩写 0x7f.0.0.1 0x7f000001
  • 十进制缩写 2130706433
  • 省略0 127.1 0177.1 0x7f.1
  • 127其实是127/8的,所以127.233.233.233也是支持的,并不是只有127.0.0.1
  • 这一种比较少见,也很可能没有防护:在linux中,0是可以传输到本机IP的,所以也可以作为linux服务器下的一种选择方案

通过302跳转方式来绕过检测

如果检测完成没有问题的话,url还是不能直接扔到库里面请求的,因为可能不少库都支持在接收到302的HTTP时直接跳转。比如python的requests库,默认的参数为allow_redirects=True也就是在302下会自动跳转。那么我们可以在自己的vps上构造这样的内容

1
2
3
4
5
302.php
<?php
header("Location: http://127.0.0.1");
?>

然后向服务器发送链接http://vps/302.php,最终就会跳转到内网地址127.0.0.1上

所以在安全处理中,我们需要对每一次的http访问的参数都做出过滤,而不是只简单过滤一次后就扔到请求库中,这里可以模仿ph师傅的方式,采用requests中的hook支持来对库中的代码做出改善,对每一次请求的参数都进行过滤

CRLF

orange师傅同样整理了一份当前存在CRLF漏洞的名单出来,在攻击时首先将请求指向自己的vps看看服务端是用什么发出的请求,如果在库里面,就存在CRLF可以利用

可以看到常见的urllib2,wget,NodeJS http,Java net.URL都存在这种问题

利用CRLF可以实现的一些东西

  • %0d%0a代表http中的一个换行,也就可以新引入一个header头,如果连用两次,就代表头结束了,接下来的是内容,所以可以将后面一些比较讨厌的头去掉,比如CSP2333 。 还可以写任意的传输数据,比如写一些xss啥的。

  • 自己可以添加任意的头,比如Cookie实现会话控制,比如实现一些特殊的协议,就像lctf中的实现redis的控制,orange的通过https发送SMTP报文

1
2
3
4
5
0:8000?url=https://127.0.0.1 %0d%0aHELO orange.tw%0d%0aMAIL FROM...:25
0:8000?url=http://118.89.16.36%0d%0aheaderInject:%20Inject%0d%0a%0d%0a<script>alert('123')</script>:80/x/
0:8000?url=http://118.89.16.36%0d%0aheaderInject:%20Inject:80/x/

利用发送请求库的特殊功能

  • curl支持file协议,所以url为file:///etc/passwd是可以看实现本地文件读取的

  • wget支持一些有趣的参数,比如-O 参数可以将get到的文件存放到指定的位置,最重要的是可以实现任意的重命名

  • curl中支持@参数,可以实现本地文件读取,在php5.6中就已经默认禁用了,在7.0版本后直接去掉了这个功能,我们可以通过如下语句打开@参数的功能

    curl_setopt($ch, CURLOPT_SAFE_UPLOAD, false);,之后就可以url=@index.php实现curl读取本地文件读取

  • curl支持拼接命令curl http://xxx/{2222.jpg,flag.php}

  • curl里面,如果使用file协议的话,不管host是什么,都会尝试在本地的path中寻找,所以能用用file读取本地文件时对host检查是没用的,比如lctf的签到题

ssrf中支持的协议及相关用途

待补,整理猪猪侠的16年乌云白帽大会+网上的协议介绍

ssrf出现场景及攻击思路

待补,整理猪猪侠的16年乌云白帽大会